Merge "Don't snooze other modes when user deactivates DND" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eac416a..d3e80ae 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -74,6 +74,7 @@
"android.view.inputmethod.flags-aconfig-java",
"android.webkit.flags-aconfig-java",
"android.widget.flags-aconfig-java",
+ "backstage_power_flags_lib",
"backup_flags_lib",
"camera_platform_flags_core_java_lib",
"com.android.hardware.input-aconfig-java",
@@ -641,6 +642,19 @@
],
}
+java_aconfig_library {
+ name: "android.permission.flags-aconfig-java-host",
+ aconfig_declarations: "android.permission.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ "com.android.nfcservices",
+ ],
+}
+
// SQLite
aconfig_declarations {
name: "android.database.sqlite-aconfig",
@@ -1320,3 +1334,20 @@
aconfig_declarations: "android.systemserver.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// backstage power
+aconfig_declarations {
+ name: "backstage_power_flags",
+ package: "com.android.server.power.optimization",
+ container: "system",
+ exportable: true,
+ srcs: [
+ "services/core/java/com/android/server/power/stats/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "backstage_power_flags_lib",
+ aconfig_declarations: "backstage_power_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 4f715f8..d6b303f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,7 +97,7 @@
// AIDL sources from external directories
":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
- ":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.fingerprint-V5-java-source",
":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 75e2efd2..e20f525 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -28,3 +28,13 @@
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
bug: "299329948"
}
+
+flag {
+ name: "count_quota_fix"
+ namespace: "backstage_power"
+ description: "Fix job count quota check"
+ bug: "300862949"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede2..3bb395f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1650,6 +1650,16 @@
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
@@ -1737,6 +1747,16 @@
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b..c240b3f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -70,6 +70,7 @@
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.job.ConstantsProto;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.usage.AppStandbyInternal;
@@ -512,7 +513,7 @@
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_QUOTA = 0;
+ static final int MSG_REACHED_TIME_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +525,7 @@
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_QUOTA = 4;
+ static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,6 +534,11 @@
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
+ /**
+ * An app has reached its job count quota. The message should contain a {@link UserPackage}
+ * object.
+ */
+ static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +880,46 @@
}
@VisibleForTesting
+ @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- return jobStatus.shouldTreatAsUserInitiatedJob()
+ if (!Flags.countQuotaFix()) {
+ return jobStatus.shouldTreatAsUserInitiatedJob()
+ || isTopStartedJobLocked(jobStatus)
+ || isUidInForeground(jobStatus.getSourceUid())
+ || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ }
+
+ if (jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ || isUidInForeground(jobStatus.getSourceUid())) {
+ return true;
+ }
+
+ if (standbyBucket == NEVER_INDEX) return false;
+
+ if (isQuotaFreeLocked(standbyBucket)) return true;
+
+ final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+ jobStatus.getSourcePackageName(), standbyBucket);
+ if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+ // Out of execution time quota.
+ return false;
+ }
+
+ if (standbyBucket != RESTRICTED_INDEX && mService.isCurrentlyRunningLocked(jobStatus)) {
+ // Running job is considered as within quota except for the restricted one, which
+ // requires additional constraints.
+ return true;
+ }
+
+ // Check if the app is within job count quota.
+ return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
}
@GuardedBy("mLock")
@@ -909,12 +944,11 @@
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+ && isUnderJobCountQuotaLocked(stats)
+ && isUnderSessionCountQuotaLocked(stats);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +957,7 @@
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1482,9 @@
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
+ if (Flags.countQuotaFix()) {
+ stats.bgJobCountInWindow += count;
+ }
}
}
@@ -1683,10 +1719,11 @@
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && !(Flags.countQuotaFix() && mService.isCurrentlyRunningLocked(js))) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
+ // individually. Running job need to determine its own quota status as well.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1805,9 +1842,8 @@
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2126,6 +2162,13 @@
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+ if (Flags.countQuotaFix()) {
+ final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+ mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+ if (!isUnderJobCountQuotaLocked(stats)) {
+ mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+ }
+ }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2257,7 +2300,6 @@
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
-
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2284,7 +2326,8 @@
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+ mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2344,7 @@
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2600,7 @@
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping event " + event.getEventType());
+ Slog.d(TAG, "Dropping usage event " + event.getEventType());
}
break;
}
@@ -2666,7 +2709,7 @@
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_QUOTA: {
+ case MSG_REACHED_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2728,7 @@
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2695,7 +2738,7 @@
}
break;
}
- case MSG_REACHED_EJ_QUOTA: {
+ case MSG_REACHED_EJ_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2756,7 @@
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2723,6 +2766,18 @@
}
break;
}
+ case MSG_REACHED_COUNT_QUOTA: {
+ UserPackage pkg = (UserPackage) msg.obj;
+ if (DEBUG) {
+ Slog.d(TAG, pkg + " has reached its count quota.");
+ }
+
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(),
+ pkg.userId, pkg.packageName));
+ break;
+ }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 613678b..410074e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,6 +1989,9 @@
mAdminProtectedPackages.put(userId, packageNames);
}
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ postCheckIdleStates(userId);
+ }
}
@Override
diff --git a/config/Android.bp b/config/Android.bp
index 6a6f848..dd681ca 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,3 +33,9 @@
name: "preloaded-classes-denylist",
srcs: ["preloaded-classes-denylist"],
}
+
+prebuilt_etc {
+ name: "dirty-image-objects",
+ src: "dirty-image-objects",
+ filename: "dirty-image-objects",
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index c86e4cd..c7fce1b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26804,7 +26804,6 @@
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
- field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -32743,7 +32742,7 @@
field public static final int S_V2 = 32; // 0x20
field public static final int TIRAMISU = 33; // 0x21
field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
- field @FlaggedApi("android.os.android_os_build_vanilla_ice_cream") public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
+ field public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0dab3de..5e9fdfb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1349,12 +1349,26 @@
public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
/**
- * The most restricted level where the apps are considered "in-hibernation",
- * its package visibility to the rest of the system is limited.
+ * The restricted level where the apps are in a force-stopped state.
*
* @hide
*/
- public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+ public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;
+
+ /**
+ * The heavily background restricted level, where apps cannot start without an explicit
+ * launch by the user.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;
+
+ /**
+ * A reserved restriction level that is not well-defined.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_CUSTOM = 90;
/**
* Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@
RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
- RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_FORCE_STOPPED,
+ RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
+ RESTRICTION_LEVEL_CUSTOM,
RESTRICTION_LEVEL_MAX,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RestrictionLevel{}
+ /**
+ * Maximum string length for sub reason for restriction.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
+
+ /**
+ * Restriction reason unknown - do not use directly.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_UNKNOWN = 0;
+
+ /**
+ * Restriction reason to be used when this is normal behavior for the state.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DEFAULT = 1;
+
+ /**
+ * Restriction reason is some kind of timeout that moves the app to a more restricted state.
+ * The threshold should specify how long the app was dormant, in milliseconds.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DORMANT = 2;
+
+ /**
+ * Restriction reason to be used when removing a restriction due to direct or indirect usage
+ * of the app, especially to undo any automatic restrictions.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USAGE = 3;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app, through
+ * UI or command line interface.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER = 4;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app on being
+ * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
+ * high battery drain or affecting system performance and the OS recommends that the user
+ * restrict the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER_NUDGED = 5;
+
+ /**
+ * Restriction reason to be used when the OS automatically detects that the app is causing
+ * system health issues such as performance degradation, battery drain, high memory usage, etc.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+
+ /**
+ * Restriction reason to be used when there is a server-side decision made to restrict an app
+ * that is showing widespread problems on user devices, or violating policy in some way.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+
+ /**
+ * Restriction reason to be used when some other problem requires restricting the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_OTHER = 8;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
+ RESTRICTION_REASON_UNKNOWN,
+ RESTRICTION_REASON_DEFAULT,
+ RESTRICTION_REASON_DORMANT,
+ RESTRICTION_REASON_USAGE,
+ RESTRICTION_REASON_USER,
+ RESTRICTION_REASON_USER_NUDGED,
+ RESTRICTION_REASON_SYSTEM_HEALTH,
+ RESTRICTION_REASON_REMOTE_TRIGGER,
+ RESTRICTION_REASON_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionReason{}
+
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@
return "restricted_bucket";
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return "background_restricted";
- case RESTRICTION_LEVEL_HIBERNATION:
- return "hibernation";
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
+ return "stopped";
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
+ return "user_only";
+ case RESTRICTION_LEVEL_CUSTOM:
+ return "custom";
case RESTRICTION_LEVEL_MAX:
return "max";
default:
- return "";
+ return String.valueOf(level);
}
}
@@ -6127,6 +6249,43 @@
}
/**
+ * Requests the system to log the reason for restricting/unrestricting an app. This API
+ * should be called before applying any change to the restriction level.
+ * <p>
+ * The {@code enabled} value determines whether the state is being applied or removed.
+ * Not all restrictions are actual restrictions. For example,
+ * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+ * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
+ * app is being put in a power-save allowlist.
+ *
+ * @param packageName the package name of the app
+ * @param uid the uid of the app
+ * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
+ * @param enabled whether the state is being applied or removed
+ * @param reason the reason for the restriction state change, from {@code RestrictionReason}
+ * @param subReason a string sub reason limited to 16 characters that specifies additional
+ * information about the reason for restriction.
+ * @param threshold for reasons that are due to exceeding some threshold, the threshold value
+ * must be specified. The unit of the threshold depends on the reason and/or
+ * subReason. For time, use milliseconds. For memory, use KB. For count, use
+ * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.DEVICE_POWER)
+ public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
+ @RestrictionLevel int restrictionLevel, boolean enabled,
+ @RestrictionReason int reason,
+ @Nullable String subReason, long threshold) {
+ try {
+ getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
+ reason, subReason, threshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies {@link #getRunningAppProcesses app processes} that the system properties
* have changed.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eaa23b9..bc66127 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1000,6 +1000,7 @@
boolean autoStopProfiler;
boolean streamingOutput;
int mClockType;
+ int mProfilerOutputVersion;
boolean profiling;
boolean handlingProfiling;
public void setProfiler(ProfilerInfo profilerInfo) {
@@ -1027,6 +1028,7 @@
autoStopProfiler = profilerInfo.autoStopProfiler;
streamingOutput = profilerInfo.streamingOutput;
mClockType = profilerInfo.clockType;
+ mProfilerOutputVersion = profilerInfo.profilerOutputVersion;
}
public void startProfiling() {
if (profileFd == null || profiling) {
@@ -1034,9 +1036,11 @@
}
try {
int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
+ int flags = 0;
+ flags = mClockType | ProfilerInfo.getFlagsForOutputVersion(mProfilerOutputVersion);
VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
- bufferSize * 1024 * 1024, mClockType, samplingInterval != 0,
- samplingInterval, streamingOutput);
+ bufferSize * 1024 * 1024, flags, samplingInterval != 0, samplingInterval,
+ streamingOutput);
profiling = true;
} catch (RuntimeException e) {
Slog.w(TAG, "Profiling failed on path " + profileFile, e);
@@ -7204,6 +7208,7 @@
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
mProfiler.mClockType = data.initProfilerInfo.clockType;
+ mProfiler.mProfilerOutputVersion = data.initProfilerInfo.profilerOutputVersion;
if (data.initProfilerInfo.attachAgentDuringBind) {
agent = data.initProfilerInfo.agent;
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index dca164d..3765c81 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1009,4 +1009,12 @@
* @param originatingUid The UID of the instrumented app that initialized the override
*/
void clearAllOverridePermissionStates(int originatingUid);
+
+ /**
+ * Request the system to log the reason for restricting / unrestricting an app.
+ * @see ActivityManager#noteAppRestrictionEnabled
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
+ void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
+ boolean enabled, int reason, in String subReason, long threshold);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6ff1bfc5..e39928b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3090,6 +3090,25 @@
}
/**
+ * @hide
+ */
+ public int loadHeaderAppIconRes(Context context) {
+ ApplicationInfo info = null;
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ info = extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ }
+ if (info == null) {
+ info = context.getApplicationInfo();
+ }
+ if (info != null) {
+ return info.icon;
+ }
+ return 0;
+ }
+
+ /**
* Removes heavyweight parts of the Notification object for archival or for sending to
* listeners when the full contents are not necessary.
* @hide
@@ -5963,12 +5982,21 @@
}
private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
- if (mN.mSmallIcon == null && mN.icon != 0) {
+ if (Flags.notificationsUseAppIcon()) {
+ // Override small icon with app icon
+ mN.mSmallIcon = Icon.createWithResource(mContext,
+ mN.loadHeaderAppIconRes(mContext));
+ } else if (mN.mSmallIcon == null && mN.icon != 0) {
mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
}
+
contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
- processSmallIconColor(mN.mSmallIcon, contentView, p);
+
+ // Don't change color if we're using the app icon.
+ if (!Flags.notificationsUseAppIcon()) {
+ processSmallIconColor(mN.mSmallIcon, contentView, p);
+ }
}
/**
@@ -6804,7 +6832,8 @@
*/
private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
StandardTemplateParams p) {
- boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
+ boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext,
+ smallIcon);
int color = getSmallIconColor(p);
contentView.setInt(R.id.icon, "setBackgroundColor",
getBackgroundColor(p));
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index f7a3d78..bcae22a 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -32,7 +32,8 @@
* {@hide}
*/
public class ProfilerInfo implements Parcelable {
-
+ // Version of the profiler output
+ public static final int OUTPUT_VERSION_DEFAULT = 1;
// CLOCK_TYPE_DEFAULT chooses the default used by ART. ART uses CLOCK_TYPE_DUAL by default (see
// kDefaultTraceClockSource in art/runtime/runtime_globals.h).
public static final int CLOCK_TYPE_DEFAULT = 0x000;
@@ -43,6 +44,9 @@
public static final int CLOCK_TYPE_WALL = 0x010;
public static final int CLOCK_TYPE_THREAD_CPU = 0x100;
public static final int CLOCK_TYPE_DUAL = 0x110;
+ // The second and third bits of the flags field specify the trace format version. This should
+ // match with kTraceFormatVersionShift defined in art/runtime/trace.h.
+ public static final int TRACE_FORMAT_VERSION_SHIFT = 1;
private static final String TAG = "ProfilerInfo";
@@ -83,8 +87,14 @@
*/
public final int clockType;
+ /**
+ * Indicates the version of profiler output.
+ */
+ public final int profilerOutputVersion;
+
public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
- boolean streaming, String agent, boolean attachAgentDuringBind, int clockType) {
+ boolean streaming, String agent, boolean attachAgentDuringBind, int clockType,
+ int profilerOutputVersion) {
profileFile = filename;
profileFd = fd;
samplingInterval = interval;
@@ -93,6 +103,7 @@
this.clockType = clockType;
this.agent = agent;
this.attachAgentDuringBind = attachAgentDuringBind;
+ this.profilerOutputVersion = profilerOutputVersion;
}
public ProfilerInfo(ProfilerInfo in) {
@@ -104,6 +115,7 @@
agent = in.agent;
attachAgentDuringBind = in.attachAgentDuringBind;
clockType = in.clockType;
+ profilerOutputVersion = in.profilerOutputVersion;
}
/**
@@ -125,13 +137,29 @@
}
/**
+ * Get the flags that need to be passed to VMDebug.startMethodTracing to specify the desired
+ * output format.
+ */
+ public static int getFlagsForOutputVersion(int version) {
+ // Only two version 1 and version 2 are supported. Just use the default if we see an unknown
+ // version.
+ if (version != 1 || version != 2) {
+ version = OUTPUT_VERSION_DEFAULT;
+ }
+
+ // The encoded version in the flags starts from 0, where as the version that we read from
+ // user starts from 1. So, subtract one before encoding it in the flags.
+ return (version - 1) << TRACE_FORMAT_VERSION_SHIFT;
+ }
+
+ /**
* Return a new ProfilerInfo instance, with fields populated from this object,
* and {@link agent} and {@link attachAgentDuringBind} as given.
*/
public ProfilerInfo setAgent(String agent, boolean attachAgentDuringBind) {
return new ProfilerInfo(this.profileFile, this.profileFd, this.samplingInterval,
this.autoStopProfiler, this.streamingOutput, agent, attachAgentDuringBind,
- this.clockType);
+ this.clockType, this.profilerOutputVersion);
}
/**
@@ -172,6 +200,7 @@
out.writeString(agent);
out.writeBoolean(attachAgentDuringBind);
out.writeInt(clockType);
+ out.writeInt(profilerOutputVersion);
}
/** @hide */
@@ -186,6 +215,7 @@
proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput);
proto.write(ProfilerInfoProto.AGENT, agent);
proto.write(ProfilerInfoProto.CLOCK_TYPE, clockType);
+ proto.write(ProfilerInfoProto.PROFILER_OUTPUT_VERSION, profilerOutputVersion);
proto.end(token);
}
@@ -211,6 +241,7 @@
agent = in.readString();
attachAgentDuringBind = in.readBoolean();
clockType = in.readInt();
+ profilerOutputVersion = in.readInt();
}
@Override
@@ -226,9 +257,9 @@
return Objects.equals(profileFile, other.profileFile)
&& autoStopProfiler == other.autoStopProfiler
&& samplingInterval == other.samplingInterval
- && streamingOutput == other.streamingOutput
- && Objects.equals(agent, other.agent)
- && clockType == other.clockType;
+ && streamingOutput == other.streamingOutput && Objects.equals(agent, other.agent)
+ && clockType == other.clockType
+ && profilerOutputVersion == other.profilerOutputVersion;
}
@Override
@@ -240,6 +271,7 @@
result = 31 * result + (streamingOutput ? 1 : 0);
result = 31 * result + Objects.hashCode(agent);
result = 31 * result + clockType;
+ result = 31 * result + profilerOutputVersion;
return result;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1aee9fe..a9f2d74 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -317,11 +317,6 @@
public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
/**
- * Returns whether the application exemptions feature flag is enabled.
- */
- public abstract boolean isApplicationExemptionsFlagEnabled();
-
- /**
* Returns a map of admin to {@link Bundle} map of restrictions set by the admins for the
* provided {@code packageName} in the provided {@code userId}
*/
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6a07484..ac843cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -195,6 +195,25 @@
}
}
+flag {
+ name: "power_exemption_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
+ bug: "333379020"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "disallow_user_control_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
+ bug: "326031059"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "esim_management_ux_enabled"
@@ -228,6 +247,16 @@
}
flag {
+ name: "always_persist_do"
+ namespace: "enterprise"
+ description: "Always write device_owners2.xml so that migration flags aren't lost"
+ bug: "335232744"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "is_recursive_required_app_merging_enabled"
namespace: "enterprise"
description: "Guards a new flow for recursive required enterprise app list merging"
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 250953e..28afd5f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -1,5 +1,9 @@
package: "android.app"
+# Note: When adding a new flag here, consider including the word "notification(s)" in the flag name
+# when appropriate, as it's not currently part of the namespace so it may not be obvious what the
+# flag relates to.
+
flag {
name: "modes_api"
is_exported: true
@@ -41,6 +45,13 @@
}
flag {
+ name: "notifications_use_app_icon"
+ namespace: "systemui"
+ description: "Experiment to replace the small icon in a notification with the app icon."
+ bug: "335211019"
+}
+
+flag {
name: "keyguard_private_notifications"
namespace: "systemui"
description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4963a4f..321e539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -171,6 +171,13 @@
}
flag {
+ name: "schedule_stop_of_background_user"
+ namespace: "multiuser"
+ description: "Schedule background users to be stopped at a future point."
+ bug: "330351042"
+}
+
+flag {
name: "disable_private_space_items_on_home"
namespace: "profile_experiences"
description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 4a4d451..7b452a8 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -74,7 +74,7 @@
* and {@link HardwareBuffer.Format} is supported on the device.
*
* @return True if the device can support efficiently compositing the content described by the
- * dataspace and format. False if GPOU composition fallback is otherwise required.
+ * dataspace and format. False if GPU composition fallback is otherwise required.
*/
@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean isCombinationSupported(@DataSpace.ColorDataSpace int dataspace,
@@ -135,7 +135,6 @@
private static native long nGetDestructor();
private static native long nCreateDefault();
- private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native boolean nIsCombinationSupported(
long nativeObject, int dataspace, int format);
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 770448b..fc72db3 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -221,7 +221,8 @@
FINGERPRINT_ACQUIRED_IMMOBILE,
FINGERPRINT_ACQUIRED_TOO_BRIGHT,
FINGERPRINT_ACQUIRED_POWER_PRESSED,
- FINGERPRINT_ACQUIRED_RE_ENROLL})
+ FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL,
+ FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintAcquired {}
@@ -316,7 +317,13 @@
* This message is sent to encourage the user to re-enroll their fingerprints.
* @hide
*/
- int FINGERPRINT_ACQUIRED_RE_ENROLL = 12;
+ int FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL = 12;
+
+ /**
+ * This message is sent to force the user to re-enroll their fingerprints.
+ * @hide
+ */
+ int FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED = 13;
/**
* @hide
diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java
new file mode 100644
index 0000000..b69024f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceCallback.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.face;
+
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE;
+import static android.hardware.face.FaceManager.getAuthHelpMessage;
+import static android.hardware.face.FaceManager.getEnrollHelpMessage;
+import static android.hardware.face.FaceManager.getErrorString;
+
+import android.content.Context;
+import android.hardware.biometrics.CryptoObject;
+import android.hardware.face.FaceManager.AuthenticationCallback;
+import android.hardware.face.FaceManager.EnrollmentCallback;
+import android.hardware.face.FaceManager.FaceDetectionCallback;
+import android.hardware.face.FaceManager.GenerateChallengeCallback;
+import android.hardware.face.FaceManager.GetFeatureCallback;
+import android.hardware.face.FaceManager.RemovalCallback;
+import android.hardware.face.FaceManager.SetFeatureCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each face related request.
+ * @hide
+ */
+public class FaceCallback {
+ private static final String TAG = " FaceCallback";
+
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FaceDetectionCallback mFaceDetectionCallback;
+ @Nullable
+ private SetFeatureCallback mSetFeatureCallback;
+ @Nullable
+ private GetFeatureCallback mGetFeatureCallback;
+ @Nullable
+ private Face mRemovalFace;
+ @Nullable
+ private CryptoObject mCryptoObject;
+
+ /**
+ * Construction for face authentication client callback.
+ */
+ FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for face detect client callback.
+ */
+ FaceCallback(FaceDetectionCallback faceDetectionCallback) {
+ mFaceDetectionCallback = faceDetectionCallback;
+ }
+
+ /**
+ * Construction for face enroll client callback.
+ */
+ FaceCallback(EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for face generate challenge client callback.
+ */
+ FaceCallback(GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for face set feature client callback.
+ */
+ FaceCallback(SetFeatureCallback setFeatureCallback) {
+ mSetFeatureCallback = setFeatureCallback;
+ }
+
+ /**
+ * Construction for face get feature client callback.
+ */
+ FaceCallback(GetFeatureCallback getFeatureCallback) {
+ mGetFeatureCallback = getFeatureCallback;
+ }
+
+ /**
+ * Construction for single face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback, Face removalFace) {
+ mRemovalCallback = removalCallback;
+ mRemovalFace = removalFace;
+ }
+
+ /**
+ * Construction for all face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback) {
+ mRemovalCallback = removalCallback;
+ }
+
+ /**
+ * Propagate set feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param feature the feature that was set
+ */
+ public void sendSetFeatureCompleted(boolean success, int feature) {
+ if (mSetFeatureCallback == null) {
+ return;
+ }
+ mSetFeatureCallback.onCompleted(success, feature);
+ }
+
+ /**
+ * Propagate get feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param features list of features available
+ * @param featureState status of the features corresponding to the previous parameter
+ */
+ public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
+ if (mGetFeatureCallback == null) {
+ return;
+ }
+ mGetFeatureCallback.onCompleted(success, features, featureState);
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(int sensorId, int userId, long challenge) {
+ if (mGenerateChallengeCallback == null) {
+ return;
+ }
+ mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate face detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFaceDetectionCallback == null) {
+ Slog.e(TAG, "sendFaceDetected, callback null");
+ return;
+ }
+ mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param face removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(Face face, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+ mRemovalCallback.onRemovalSucceeded(face, remaining);
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+ ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFaceDetectionCallback != null) {
+ mFaceDetectionCallback.onDetectionError(errMsgId);
+ mFaceDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param face matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
+ if (mAuthenticationCallback != null) {
+ final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult(
+ mCryptoObject, face, userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendAuthenticationFrame(context, frame);
+ } else if (mEnrollmentCallback != null) {
+ final FaceEnrollFrame frame = new FaceEnrollFrame(
+ null /* cell */,
+ FaceEnrollStages.UNKNOWN,
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendEnrollmentFrame(context, frame);
+ }
+ }
+
+ /**
+ * Propagate authentication frame via the callback.
+ * @param context corresponding context
+ * @param frame authentication frame to be sent
+ */
+ public void sendAuthenticationFrame(@NonNull Context context,
+ @Nullable FaceAuthenticationFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null authentication frame");
+ } else if (mAuthenticationCallback != null) {
+ // TODO(b/178414967): Send additional frame data to callback
+ final int acquireInfo = frame.getData().getAcquiredInfo();
+ final int vendorCode = frame.getData().getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode);
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+ // Ensure that only non-null help messages are sent.
+ if (helpMessage != null) {
+ mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+ }
+ }
+ }
+
+ /**
+ * Propagate enrollment via the callback.
+ * @param context corresponding context
+ * @param frame enrollment frame to be sent
+ */
+ public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null enrollment frame");
+ } else if (mEnrollmentCallback != null) {
+ final FaceDataFrame data = frame.getData();
+ final int acquireInfo = data.getAcquiredInfo();
+ final int vendorCode = data.getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode);
+ mEnrollmentCallback.onEnrollmentFrame(
+ helpCode,
+ helpMessage,
+ frame.getCell(),
+ frame.getStage(),
+ data.getPan(),
+ data.getTilt(),
+ data.getDistance());
+ }
+ }
+
+ private static int getHelpCode(int acquireInfo, int vendorCode) {
+ return acquireInfo == FACE_ACQUIRED_VENDOR
+ ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+ : acquireInfo;
+ }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 210ce2b..2592630 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -37,9 +37,9 @@
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -49,7 +49,6 @@
import android.view.Surface;
import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
@@ -63,71 +62,56 @@
private static final String TAG = "FaceManager";
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_GET_FEATURE_COMPLETED = 106;
- private static final int MSG_SET_FEATURE_COMPLETED = 107;
- private static final int MSG_CHALLENGE_GENERATED = 108;
- private static final int MSG_FACE_DETECTED = 109;
- private static final int MSG_AUTHENTICATION_FRAME = 112;
- private static final int MSG_ENROLLMENT_FRAME = 113;
-
private final IFaceService mService;
private final Context mContext;
private final IBinder mToken = new Binder();
- @Nullable private AuthenticationCallback mAuthenticationCallback;
- @Nullable private FaceDetectionCallback mFaceDetectionCallback;
- @Nullable private EnrollmentCallback mEnrollmentCallback;
- @Nullable private RemovalCallback mRemovalCallback;
- @Nullable private SetFeatureCallback mSetFeatureCallback;
- @Nullable private GetFeatureCallback mGetFeatureCallback;
- @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- private Face mRemovalFace;
private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
- private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+ private class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
+ private final FaceCallback mFaceCallback;
+
+ FaceServiceReceiver(FaceCallback faceCallback) {
+ mFaceCallback = faceCallback;
+ }
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
- isStrongBiometric ? 1 : 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticatedSucceeded(face, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendFaceDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFaceCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendErrorResult(mContext, error, vendorCode));
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendRemovedResult(face, remaining));
if (remaining == 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
@@ -137,34 +121,31 @@
@Override
public void onFeatureSet(boolean success, int feature) {
- mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendSetFeatureCompleted(success, feature));
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = success;
- args.arg2 = features;
- args.arg3 = featureState;
- mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendGetFeatureCompleted(success, features,
+ featureState));
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendChallengeGenerated(sensorId, userId,
+ challenge));
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticationFrame(mContext, frame));
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollmentFrame(mContext, frame));
}
- };
+ }
/**
* @hide
@@ -175,7 +156,8 @@
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mHandler = new MyHandler(context);
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -193,9 +175,11 @@
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -249,13 +233,12 @@
if (mService != null) {
try {
+ final FaceCallback faceCallback = new FaceCallback(callback, crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
final long authId = mService.authenticate(
- mToken, operationId, mServiceReceiver, options);
+ mToken, operationId, new FaceServiceReceiver(faceCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -292,10 +275,11 @@
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFaceDetectionCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
try {
- final long authId = mService.detectFace(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFace(mToken,
+ new FaceServiceReceiver(faceCallback), options);
cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -367,11 +351,11 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enroll");
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
- previewSurface, debugConsent, options);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures, previewSurface, debugConsent, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -419,10 +403,11 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enrollRemotely");
final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
}
@@ -455,9 +440,9 @@
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) {
try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -528,9 +513,9 @@
SetFeatureCallback callback) {
if (mService != null) {
try {
- mSetFeatureCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
mService.setFeature(mToken, userId, feature, enabled, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName());
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -544,8 +529,8 @@
public void getFeature(int userId, int feature, GetFeatureCallback callback) {
if (mService != null) {
try {
- mGetFeatureCallback = callback;
- mService.getFeature(mToken, userId, feature, mServiceReceiver,
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.getFeature(mToken, userId, feature, new FaceServiceReceiver(faceCallback),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -566,10 +551,9 @@
public void remove(Face face, int userId, RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemovalFace = face;
- mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback, face);
+ mService.remove(mToken, face.getBiometricId(), userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -584,8 +568,9 @@
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.removeAll(mToken, userId, new FaceServiceReceiver(faceCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1270,203 +1255,6 @@
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_SET_FEATURE_COMPLETED:
- sendSetFeatureCompleted((boolean) msg.obj /* success */,
- msg.arg1 /* feature */);
- break;
- case MSG_GET_FEATURE_COMPLETED:
- SomeArgs args = (SomeArgs) msg.obj;
- sendGetFeatureCompleted((boolean) args.arg1 /* success */,
- (int[]) args.arg2 /* features */,
- (boolean[]) args.arg3 /* featureState */);
- args.recycle();
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FACE_DETECTED:
- sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FRAME:
- sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
- break;
- case MSG_ENROLLMENT_FRAME:
- sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
- break;
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
- }
- Trace.endSection();
- }
- }
-
- private void sendSetFeatureCompleted(boolean success, int feature) {
- if (mSetFeatureCallback == null) {
- return;
- }
- mSetFeatureCallback.onCompleted(success, feature);
- }
-
- private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
- if (mGetFeatureCallback == null) {
- return;
- }
- mGetFeatureCallback.onCompleted(success, features, featureState);
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- return;
- }
- mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
- }
-
- private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFaceDetectionCallback == null) {
- Slog.e(TAG, "sendFaceDetected, callback null");
- return;
- }
- mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendRemovedResult(Face face, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
- mRemovalCallback.onRemovalSucceeded(face, remaining);
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
- ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFaceDetectionCallback != null) {
- mFaceDetectionCallback.onDetectionError(errMsgId);
- mFaceDetectionCallback = null;
- }
- }
-
- private void sendEnrollResult(Face face, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
- new FaceDataFrame(acquireInfo, vendorCode));
- sendAuthenticationFrame(frame);
- } else if (mEnrollmentCallback != null) {
- final FaceEnrollFrame frame = new FaceEnrollFrame(
- null /* cell */,
- FaceEnrollStages.UNKNOWN,
- new FaceDataFrame(acquireInfo, vendorCode));
- sendEnrollmentFrame(frame);
- }
- }
-
- private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null authentication frame");
- } else if (mAuthenticationCallback != null) {
- // TODO(b/178414967): Send additional frame data to callback
- final int acquireInfo = frame.getData().getAcquiredInfo();
- final int vendorCode = frame.getData().getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
-
- // Ensure that only non-null help messages are sent.
- if (helpMessage != null) {
- mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
- }
- }
- }
-
- private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null enrollment frame");
- } else if (mEnrollmentCallback != null) {
- final FaceDataFrame data = frame.getData();
- final int acquireInfo = data.getAcquiredInfo();
- final int vendorCode = data.getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
- mEnrollmentCallback.onEnrollmentFrame(
- helpCode,
- helpMessage,
- frame.getCell(),
- frame.getStage(),
- data.getPan(),
- data.getTilt(),
- data.getDistance());
- }
- }
-
- private static int getHelpCode(int acquireInfo, int vendorCode) {
- return acquireInfo == FACE_ACQUIRED_VENDOR
- ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
- : acquireInfo;
- }
-
/**
* @hide
*/
diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java
new file mode 100644
index 0000000..e4fbe6e
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE;
+import static android.hardware.fingerprint.FingerprintManager.getAcquiredString;
+import static android.hardware.fingerprint.FingerprintManager.getErrorString;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback;
+import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback;
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each fingerprint related request.
+ * @hide
+ */
+public class FingerprintCallback {
+ private static final String TAG = "FingerprintCallback";
+ public static final int REMOVE_SINGLE = 1;
+ public static final int REMOVE_ALL = 2;
+ @IntDef({REMOVE_SINGLE, REMOVE_ALL})
+ public @interface RemoveRequest {}
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FingerprintDetectionCallback mFingerprintDetectionCallback;
+ @Nullable
+ private CryptoObject mCryptoObject;
+ @Nullable
+ private @RemoveRequest int mRemoveRequest;
+ @Nullable
+ private Fingerprint mRemoveFingerprint;
+
+ /**
+ * Construction for fingerprint authentication client callback.
+ */
+ FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback,
+ @Nullable CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for fingerprint detect client callback.
+ */
+ FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) {
+ mFingerprintDetectionCallback = fingerprintDetectionCallback;
+ }
+
+ /**
+ * Construction for fingerprint enroll client callback.
+ */
+ FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for fingerprint generate challenge client callback.
+ */
+ FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for fingerprint removal client callback.
+ */
+ FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest,
+ @Nullable Fingerprint removeFingerprint) {
+ mRemovalCallback = removalCallback;
+ mRemoveRequest = removeRequest;
+ mRemoveFingerprint = removeFingerprint;
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param fingerprint removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+
+ if (mRemoveRequest == REMOVE_SINGLE) {
+ if (fingerprint == null) {
+ Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
+ return;
+ }
+
+ if (mRemoveFingerprint == null) {
+ Slog.e(TAG, "Missing fingerprint");
+ return;
+ }
+
+ final int fingerId = fingerprint.getBiometricId();
+ int reqFingerId = mRemoveFingerprint.getBiometricId();
+ if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
+ Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
+ return;
+ }
+ }
+
+ mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param fingerprint matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId,
+ boolean isStrongBiometric) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "Authentication succeeded but callback is null.");
+ return;
+ }
+
+ final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint,
+ userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+ }
+ if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
+ }
+ final String msg = getAcquiredString(context, acquireInfo, vendorCode);
+ if (msg == null) {
+ return;
+ }
+ // emulate HAL 2.1 behavior and send real acquiredInfo
+ final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+ } else if (mAuthenticationCallback != null) {
+ if (acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+ }
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
+ ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFingerprintDetectionCallback != null) {
+ mFingerprintDetectionCallback.onDetectionError(errMsgId);
+ mFingerprintDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(long challenge, int sensorId, int userId) {
+ if (mGenerateChallengeCallback == null) {
+ Slog.e(TAG, "sendChallengeGenerated, callback null");
+ return;
+ }
+ mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate fingerprint detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFingerprintDetectionCallback == null) {
+ Slog.e(TAG, "sendFingerprintDetected, callback null");
+ return;
+ }
+ mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate udfps pointer down via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerDown(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerDown, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerDown(sensorId);
+ }
+
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerDown(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps pointer up via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerUp(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerUp, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerUp(sensorId);
+ }
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerUp(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps overlay shown via the callback.
+ */
+ public void sendUdfpsOverlayShown() {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsOverlayShown();
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..37f2fb2 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -25,6 +25,8 @@
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -57,9 +59,9 @@
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -94,19 +96,6 @@
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
- private static final boolean DEBUG = true;
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_CHALLENGE_GENERATED = 106;
- private static final int MSG_FINGERPRINT_DETECTED = 107;
- private static final int MSG_UDFPS_POINTER_DOWN = 108;
- private static final int MSG_UDFPS_POINTER_UP = 109;
- private static final int MSG_POWER_BUTTON_PRESSED = 110;
- private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
/**
* @hide
@@ -148,34 +137,14 @@
*/
public static final int SENSOR_ID_ANY = -1;
- private static class RemoveTracker {
- static final int REMOVE_SINGLE = 1;
- static final int REMOVE_ALL = 2;
- @IntDef({REMOVE_SINGLE, REMOVE_ALL})
- @interface RemoveRequest {}
+ private final IFingerprintService mService;
+ private final Context mContext;
+ private final IBinder mToken = new Binder();
- final @RemoveRequest int mRemoveRequest;
- @Nullable final Fingerprint mSingleFingerprint;
-
- RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) {
- mRemoveRequest = request;
- mSingleFingerprint = fingerprint;
- }
- }
-
- private IFingerprintService mService;
- private Context mContext;
- private IBinder mToken = new Binder();
- private AuthenticationCallback mAuthenticationCallback;
- private FingerprintDetectionCallback mFingerprintDetectionCallback;
- private EnrollmentCallback mEnrollmentCallback;
- private RemovalCallback mRemovalCallback;
- private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- @Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
@Nullable private float[] mEnrollStageThresholds;
private List<FingerprintSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -395,7 +364,7 @@
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
- public static abstract class AuthenticationCallback
+ public abstract static class AuthenticationCallback
extends BiometricAuthenticator.AuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
@@ -479,7 +448,7 @@
*
* @hide
*/
- public static abstract class EnrollmentCallback {
+ public abstract static class EnrollmentCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
@@ -536,7 +505,7 @@
*
* @hide
*/
- public static abstract class RemovalCallback {
+ public abstract static class RemovalCallback {
/**
* Called when the given fingerprint can't be removed.
* @param fp The fingerprint that the call attempted to remove
@@ -559,7 +528,7 @@
/**
* @hide
*/
- public static abstract class LockoutResetCallback {
+ public abstract static class LockoutResetCallback {
/**
* Called when lockout period expired and clients are allowed to listen for fingerprint
@@ -584,9 +553,11 @@
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -676,11 +647,12 @@
if (mService != null) {
try {
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- final long authId = mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(mToken, operationId,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -715,10 +687,11 @@
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFingerprintDetectionCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
try {
- final long authId = mService.detectFingerprint(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFingerprint(mToken,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -767,9 +740,10 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
- mServiceReceiver, mContext.getOpPackageName(), enrollReason, options);
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName(), enrollReason, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -799,12 +773,13 @@
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -875,13 +850,14 @@
@RequiresPermission(MANAGE_FINGERPRINT)
public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
if (mService != null) try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp);
- mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_SINGLE, fp);
+ mService.remove(mToken, fp.getBiometricId(), userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -892,9 +868,11 @@
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */);
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_ALL, null);
+ mService.removeAll(mToken, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1162,7 +1140,7 @@
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPowerPressed() {
Slog.i(TAG, "onPowerPressed");
- mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+ mExecutor.execute(() -> sendPowerPressed());
}
/**
@@ -1346,199 +1324,6 @@
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */,
- msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FINGERPRINT_DETECTED:
- sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_UDFPS_POINTER_DOWN:
- sendUdfpsPointerDown(msg.arg1 /* sensorId */);
- break;
- case MSG_UDFPS_POINTER_UP:
- sendUdfpsPointerUp(msg.arg1 /* sensorId */);
- break;
- case MSG_POWER_BUTTON_PRESSED:
- sendPowerPressed();
- break;
- case MSG_UDFPS_OVERLAY_SHOWN:
- sendUdfpsOverlayShown();
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
-
- }
- }
- }
-
- private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
-
- if (mRemoveTracker == null) {
- Slog.w(TAG, "Removal tracker is null");
- return;
- }
-
- if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) {
- if (fingerprint == null) {
- Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
- return;
- }
-
- if (mRemoveTracker.mSingleFingerprint == null) {
- Slog.e(TAG, "Missing fingerprint");
- return;
- }
-
- final int fingerId = fingerprint.getBiometricId();
- int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId();
- if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
- Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
- return;
- }
- }
-
- mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
- }
-
- private void sendEnrollResult(Fingerprint fp, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
- }
- if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
- mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
- }
- final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
- if (msg == null) {
- return;
- }
- // emulate HAL 2.1 behavior and send real acquiredInfo
- final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
- ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
- } else if (mAuthenticationCallback != null) {
- if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
- }
- }
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
- ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- final Fingerprint fp = mRemoveTracker != null
- ? mRemoveTracker.mSingleFingerprint : null;
- mRemovalCallback.onRemovalError(fp, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFingerprintDetectionCallback != null) {
- mFingerprintDetectionCallback.onDetectionError(errMsgId);
- mFingerprintDetectionCallback = null;
- }
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- Slog.e(TAG, "sendChallengeGenerated, callback null");
- return;
- }
- mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
- }
-
- private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFingerprintDetectionCallback == null) {
- Slog.e(TAG, "sendFingerprintDetected, callback null");
- return;
- }
- mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendUdfpsPointerDown(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerDown, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerDown(sensorId);
- }
-
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerDown(sensorId);
- }
- }
-
- private void sendUdfpsPointerUp(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerUp, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerUp(sensorId);
- }
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerUp(sensorId);
- }
- }
-
private void sendPowerPressed() {
try {
mService.onPowerPressed();
@@ -1547,12 +1332,6 @@
}
}
- private void sendUdfpsOverlayShown() {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsOverlayShown();
- }
- }
-
/**
* @hide
*/
@@ -1562,7 +1341,6 @@
if (mService == null) {
Slog.v(TAG, "FingerprintService was null");
}
- mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(
@@ -1574,6 +1352,8 @@
}
});
}
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
private int getCurrentUserId() {
@@ -1773,66 +1553,72 @@
return null;
}
- private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
+ class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub {
+ private final FingerprintCallback mFingerprintCallback;
+
+ FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) {
+ mFingerprintCallback = fingerprintCallback;
+ }
@Override // binder call
public void onEnrollResult(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Fingerprint fp, int userId,
boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
- fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId,
+ isStrongBiometric));
}
@Override
public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error,
+ vendorCode));
}
@Override // binder call
public void onRemoved(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining));
}
@Override // binder call
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId,
+ userId));
}
@Override // binder call
public void onUdfpsPointerDown(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId));
}
@Override // binder call
public void onUdfpsPointerUp(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId));
}
@Override
public void onUdfpsOverlayShown() {
- mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown);
}
- };
-
+ }
}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index d681a2c..d1531a1 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -162,12 +162,21 @@
result.putInt(IP_VERSION_KEY, params.getIpVersion());
result.putInt(ENCAP_TYPE_KEY, params.getEncapType());
- // TODO: b/185941731 Make sure IkeSessionParamsUtils is automatically updated when a new
- // IKE_OPTION is defined in IKE module and added in the IkeSessionParams
final List<Integer> enabledIkeOptions = new ArrayList<>();
- for (int option : IKE_OPTIONS) {
- if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
- enabledIkeOptions.add(option);
+
+ try {
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // com.android.ipsec.flags.enabled_ike_options_api flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ enabledIkeOptions.addAll(params.getIkeOptions());
+ } catch (Exception e) {
+ // getIkeOptions throws. It means the API is not available
+ enabledIkeOptions.clear();
+ for (int option : IKE_OPTIONS) {
+ if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
+ enabledIkeOptions.add(option);
+ }
}
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 3977bdf..2b6b358 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -17,7 +17,6 @@
package android.os;
import android.Manifest;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1230,7 +1229,6 @@
/**
* Vanilla Ice Cream.
*/
- @FlaggedApi(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
public static final int VANILLA_ICE_CREAM = CUR_DEVELOPMENT;
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 4ae0a57..3b59901 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -98,6 +98,8 @@
IBinder registerAttributionSource(in AttributionSourceState source);
+ int getNumRegisteredAttributionSources(int uid);
+
boolean isRegisteredAttributionSource(in AttributionSourceState source);
int checkPermission(String packageName, String permissionName, String persistentDeviceId,
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fe3fa8c..2daf4ac 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1675,6 +1675,21 @@
}
/**
+ * Gets the number of currently registered attribution sources for a particular UID. This should
+ * only be used for testing purposes.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS)
+ public int getNumRegisteredAttributionSourcesForTest(int uid) {
+ try {
+ return mPermissionManager.getNumRegisteredAttributionSources(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return -1;
+ }
+
+ /**
* Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
* USED in CTS or local tests.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dd93972..6ad7422 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9020,6 +9020,16 @@
"accessibility_display_daltonizer";
/**
+ * Integer property that determines the saturation level of color correction. Default value
+ * is defined in Settings config.xml.
+ * [0-10] inclusive where 0 would look as if color space adustment is not applied at all.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL =
+ "accessibility_display_daltonizer_saturation_level";
+
+ /**
* Setting that specifies whether automatic click when the mouse pointer stops moving is
* enabled.
*
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index 1a6cf88..ddb662ad 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -28,6 +28,8 @@
boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+
+ @EnforcePermission("SETUP_FSVERITY")
int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
in String packageName);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 269839b..e6a84df 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -37,6 +37,7 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.os.IResultReceiver;
@@ -641,7 +642,7 @@
@Override
public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
- IBinder autofillClientCallback) {
+ IAutoFillManagerClient autofillClientCallback) {
ICancellationSignal transport = CancellationSignal.createTransport();
try {
callback.onCancellable(transport);
@@ -723,7 +724,7 @@
*/
public void onFillCredentialRequest(@NonNull FillRequest request,
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
- @NonNull IBinder autofillClientCallback) {}
+ @NonNull IAutoFillManagerClient autofillClientCallback) {}
/**
* Called by the Android system to convert a credential manager response to a dataset
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 3b64b8a..2c2feae 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -16,13 +16,13 @@
package android.service.autofill;
-import android.os.IBinder;
import android.service.autofill.ConvertCredentialRequest;
import android.service.autofill.IConvertCredentialCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.os.IResultReceiver;
/**
@@ -34,7 +34,7 @@
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
- in IBinder client);
+ in IAutoFillManagerClient client);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ac1f646..4c4a22c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13443,6 +13443,15 @@
}
/**
+ * @return True if the window has the {@link OnContentApplyWindowInsetsListener}, and this means
+ * the framework will apply window insets on the content of the window.
+ * @hide
+ */
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mAttachInfo != null && mAttachInfo.mContentOnApplyWindowInsetsListener != null;
+ }
+
+ /**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link #fitSystemWindows(Rect)} will be
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40366c2..8d55777 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,6 +89,7 @@
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
@@ -110,12 +111,12 @@
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -1441,6 +1442,7 @@
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
+ adjustLayoutInDisplayCutoutMode(attrs);
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
@@ -2043,6 +2045,9 @@
final int appearance = mWindowAttributes.insetsFlags.appearance;
final int behavior = mWindowAttributes.insetsFlags.behavior;
+ // Calling this before copying prevents redundant LAYOUT_CHANGED.
+ final int layoutInDisplayCutoutModeFromCaller = adjustLayoutInDisplayCutoutMode(attrs);
+
final int changes = mWindowAttributes.copyFrom(attrs);
if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
@@ -2059,6 +2064,9 @@
mWindowAttributes.packageName = mBasePackageName;
}
+ // Restore the layoutInDisplayCutoutMode of the caller;
+ attrs.layoutInDisplayCutoutMode = layoutInDisplayCutoutModeFromCaller;
+
// Restore preserved flags.
mWindowAttributes.systemUiVisibility = systemUiVisibility;
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
@@ -2101,6 +2109,19 @@
}
}
+ private int adjustLayoutInDisplayCutoutMode(WindowManager.LayoutParams attrs) {
+ final int originalMode = attrs.layoutInDisplayCutoutMode;
+ if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && attrs.isFullscreen()
+ && attrs.getFitInsetsTypes() == 0
+ && attrs.getFitInsetsSides() == 0) {
+ if (originalMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+ attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ }
+ }
+ return originalMode;
+ }
+
void handleAppVisibility(boolean visible) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 12ce0f4..bdfc236 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,6 +27,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -1190,6 +1191,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1231,6 +1234,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1260,6 +1265,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
int interactionId) {
int interactionIdWaitingForPrefetchResultCopy = -1;
@@ -1324,6 +1330,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
@@ -1372,6 +1380,7 @@
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(
@AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
synchronized (mInstanceLock) {
@@ -1729,6 +1738,7 @@
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
if (!Flags.a11yOverlayCallbacks()) {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index fe59519..a9e5db5 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -34,6 +34,7 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
/**
@@ -43,6 +44,7 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
int interactionId);
@@ -52,6 +54,7 @@
* @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
* @param infos The result {@link AccessibilityNodeInfo}s.
*/
+ @RequiresNoPermission
void setPrefetchAccessibilityNodeInfoResult(
in List<AccessibilityNodeInfo> infos, int interactionId);
@@ -62,15 +65,18 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
/**
* Sends an error code for a window screenshot request to the requesting client.
*/
+ @RequiresNoPermission
void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
/**
* Sends an result code for an attach overlay request to the requesting client.
*/
+ @RequiresNoPermission
void sendAttachOverlayResult(int result, int interactionId);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 614df7c..cd11314 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -42,112 +42,154 @@
*/
interface IAccessibilityManager {
+ @RequiresNoPermission
oneway void interrupt(int userId);
+ @RequiresNoPermission
oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+ @RequiresNoPermission
long addClient(IAccessibilityManagerClient client, int userId);
+ @RequiresNoPermission
boolean removeClient(IAccessibilityManagerClient client, int userId);
+ @RequiresNoPermission
ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
+ @RequiresNoPermission
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
+ @RequiresNoPermission
int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
in IAccessibilityInteractionConnection connection,
String packageName, int userId);
+ @RequiresNoPermission
void removeAccessibilityInteractionConnection(IWindow windowToken);
+ @EnforcePermission("MODIFY_ACCESSIBILITY_DATA")
void setPictureInPictureActionReplacingConnection(
in IAccessibilityInteractionConnection connection);
+ @EnforcePermission("RETRIEVE_WINDOW_CONTENT")
void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
in AccessibilityServiceInfo info, int userId, int flags);
+ @RequiresNoPermission
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
// Used by UiAutomation
+ @EnforcePermission("RETRIEVE_WINDOW_CONTENT")
IBinder getWindowToken(int windowId, int userId);
+ @EnforcePermission("STATUS_BAR_SERVICE")
void notifyAccessibilityButtonClicked(int displayId, String targetName);
+
+ @EnforcePermission("STATUS_BAR_SERVICE")
void notifyAccessibilityButtonVisibilityChanged(boolean available);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
void performAccessibilityShortcut(String targetName);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
List<String> getAccessibilityShortcutTargets(int shortcutType);
// System process only
+ @RequiresNoPermission
boolean sendFingerprintGesture(int gestureKeyCode);
// System process only
+ @RequiresNoPermission
int getAccessibilityWindowId(IBinder windowToken);
+ @RequiresNoPermission
long getRecommendedTimeoutMillis();
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void registerSystemAction(in RemoteAction action, int actionId);
+
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void unregisterSystemAction(int actionId);
+
+ @EnforcePermission("STATUS_BAR_SERVICE")
oneway void setMagnificationConnection(in IMagnificationConnection connection);
+ @RequiresNoPermission
void associateEmbeddedHierarchy(IBinder host, IBinder embedded);
+ @RequiresNoPermission
void disassociateEmbeddedHierarchy(IBinder token);
+ @RequiresNoPermission
int getFocusStrokeWidth();
+ @RequiresNoPermission
int getFocusColor();
+ @RequiresNoPermission
boolean isAudioDescriptionByDefaultEnabled();
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+ @EnforcePermission("SET_SYSTEM_AUDIO_CAPTION")
void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId);
+ @RequiresNoPermission
boolean isSystemAudioCaptioningUiEnabled(int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+ @EnforcePermission("SET_SYSTEM_AUDIO_CAPTION")
void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId);
+ @RequiresNoPermission
oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ // Requires CREATE_VIRTUAL_DEVICE permission. Also requires either a11y permission or role.
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ // Requires CREATE_VIRTUAL_DEVICE permission. Also requires either a11y permission or role.
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean unregisterProxyForDisplay(int displayId);
// Used by UiAutomation for tests on the InputFilter
+ @EnforcePermission("INJECT_EVENTS")
void injectInputEventToInputFilter(in InputEvent event);
+ @RequiresNoPermission
boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
+
+ @RequiresNoPermission
boolean stopFlashNotificationSequence(String opPkg);
+
+ @RequiresNoPermission
boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
+ @RequiresNoPermission
boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
+
+ @RequiresNoPermission
boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info);
parcelable WindowTransformationSpec {
float[] transformationMatrix;
MagnificationSpec magnificationSpec;
}
+ @RequiresNoPermission
WindowTransformationSpec getWindowTransformationSpec(int windowId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
+ @EnforcePermission("INTERNAL_SYSTEM_WINDOW")
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
+ @EnforcePermission(allOf={"STATUS_BAR_SERVICE","MANAGE_ACCESSIBILITY"})
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void enableShortcutsForTargets(boolean enable, int shortcutTypes, in List<String> shortcutTargets, int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
Bundle getA11yFeatureToTileMap(int userId);
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 4148e00..5e88d97c 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1298,6 +1298,12 @@
if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
sb.append("focusable:" + mFocusable + ",");
}
+ if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ sb.append("forceTranslucent:" + mForceTranslucent + ",");
+ }
+ if ((mChangeMask & CHANGE_HIDDEN) != 0) {
+ sb.append("hidden:" + mHidden + ",");
+ }
if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
sb.append("dragResizing:" + mDragResizing + ",");
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4b3d8e8..e49089d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -88,4 +88,14 @@
description: "Whether to enable WM Extensions for all devices"
bug: "306666082"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "always_defer_transition_when_apply_wct"
+ description: "Report error when defer transition fails when it should not"
+ bug: "335562144"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 1841353..52487fb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2486,9 +2486,6 @@
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- if (mEdgeToEdgeEnforced) {
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- }
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index adbf645..0992db9 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -436,7 +436,7 @@
// overlay.
mContentInsets.set(mBaseContentInsets);
mInnerInsets = mBaseInnerInsets;
- if (!mOverlayMode && !stable) {
+ if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) {
mContentInsets.top += topInset;
mContentInsets.bottom += bottomInset;
// Content view has been shrunk, shrink all insets to match.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 773823d..80a7599 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_Surface.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_view_VerifiedKeyEvent.cpp",
+ "android_view_VerifiedMotionEvent.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"core_jni_helpers.cpp",
":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@
"android_view_CompositionSamplingListener.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
- "android_view_MotionEvent.cpp",
"android_view_MotionPredictor.cpp",
"android_view_PointerIcon.cpp",
- "android_view_Surface.cpp",
"android_view_SurfaceControl.cpp",
"android_view_SurfaceControlHdrLayerInfoListener.cpp",
"android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@
"android_view_SurfaceSession.cpp",
"android_view_TextureView.cpp",
"android_view_TunnelModeEnabledListener.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_text_Hyphenator.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@
"-Wno-unused-function",
],
srcs: [
- "LayoutlibLoader.cpp",
+ "platform/host/HostRuntime.cpp",
+ "platform/host/native_window_jni.cpp",
],
include_dirs: [
"external/vulkan-headers/include",
@@ -414,6 +415,7 @@
"libhostgraphics",
"libhwui",
"libimage_type_recognition",
+ "libinput",
"libjpeg",
"libpiex",
"libpng",
@@ -441,16 +443,9 @@
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
- "android_view_MotionEvent.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
@@ -458,7 +453,6 @@
"android_util_FileObserver.cpp",
],
static_libs: [
- "libinput",
"libbinderthreadstateutils",
"libsqlite",
"libgui_window_info_static",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69..3e3af40 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <sstream>
+
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -598,8 +600,8 @@
// ----------------- @CriticalNative ------------------------------
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
- jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jboolean keepHistory) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -609,8 +611,8 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
- jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jint idBits) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -621,168 +623,192 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
}
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDeviceId();
}
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getSource();
}
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint source) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setSource(source);
}
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDisplayId();
}
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->setDisplayId(displayId);
}
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getAction();
}
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint action) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setAction(action);
}
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getActionButton();
}
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setActionButton(button);
}
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->isTouchEvent();
}
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getFlags();
}
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint flags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setFlags(flags);
}
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getEdgeFlags();
}
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint edgeFlags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setEdgeFlags(edgeFlags);
}
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getMetaState();
}
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getButtonState();
}
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint buttonState) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setButtonState(buttonState);
}
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return static_cast<jint>(event->getClassification());
}
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
- jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat deltaX, jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawYOffset();
}
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXCursorPosition();
}
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYCursorPosition();
}
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setCursorPosition(x, y);
}
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDownTime();
}
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setDownTime(downTimeNanos);
}
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getPointerCount());
}
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->findPointerIndex(pointerId));
}
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getHistorySize());
}
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat scale) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->scale(scale);
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53d..ac6298d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/Log.h>
+#ifdef __ANDROID__
#include <private/android/AHardwareBufferHelpers.h>
#include "android_os_Parcel.h"
#include <binder/Parcel.h>
#include <gui/BLASTBufferQueue.h>
+#endif
#include <gui/Surface.h>
+#ifdef __ANDROID__
#include <gui/SurfaceControl.h>
#include <gui/view/Surface.h>
+#endif
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -67,6 +71,7 @@
jfieldID bottom;
} gRectClassInfo;
+#ifdef __ANDROID__
class JNamedColorSpace {
public:
// ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@
return ui::Dataspace::V0_SRGB;
}
}
+#endif
// ----------------------------------------------------------------------------
@@ -144,6 +150,7 @@
// ----------------------------------------------------------------------------
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
jobject surfaceTextureObj) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@
surface->incStrong(&sRefBaseOwner);
return jlong(surface.get());
}
+#endif
static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@
}
// ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@
// to the Parcel
surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
}
+#endif
static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@
return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
}
+#ifdef __ANDROID__
static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject hardwareBuffer, jint colorSpaceId) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@
fromNamedColorSpaceValueToDataspace(colorSpaceId));
return err;
}
+#endif
static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
jboolean enabled) {
@@ -457,8 +468,10 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
(void*)nativeCreateFromSurfaceTexture},
+#endif
{"nativeRelease", "(J)V", (void*)nativeRelease},
{"nativeIsValid", "(J)Z", (void*)nativeIsValid},
{"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@
{"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
(void*)nativeUnlockCanvasAndPost},
{"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
{"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
{"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
{"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
{"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
{"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
{"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
(void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
{"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b823..23cedb8 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
#include <android/native_window.h>
#include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
#include "jni.h"
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 0000000..10ce5cf
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
similarity index 94%
rename from core/jni/LayoutlibLoader.cpp
rename to core/jni/platform/host/HostRuntime.cpp
index 83b6afa..0433855 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@
namespace android {
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
-#define REG_JNI(name) { name }
+#define REG_JNI(name) \
+ { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.animation.PropertyValuesHolder",
REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@
};
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
- const vector<string>& classesToRegister, JNIEnv* env) {
-
+ const vector<string>& classesToRegister, JNIEnv* env) {
for (const string& className : classesToRegister) {
if (jniRegMap.at(className).mProc(env) < 0) {
return -1;
@@ -169,15 +170,14 @@
return 0;
}
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
- const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods, int numMethods) {
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
JNIEnv* AndroidRuntime::getJNIEnv() {
JNIEnv* env;
- if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
- return nullptr;
+ if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
return env;
}
@@ -186,11 +186,10 @@
}
static vector<string> parseCsv(const string& csvString) {
- vector<string> result;
+ vector<string> result;
istringstream stream(csvString);
string segment;
- while(getline(stream, segment, ','))
- {
+ while (getline(stream, segment, ',')) {
result.push_back(segment);
}
return result;
@@ -274,7 +273,9 @@
}
struct CloseHandleWrapper {
- void operator()(HANDLE h) { CloseHandle(h); }
+ void operator()(HANDLE h) {
+ CloseHandle(h);
+ }
};
std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@
// Configuration is stored as java System properties.
// Get a reference to System.getProperty
jclass system = FindClassOrDie(env, "java/lang/System");
- jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
- "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jmethodID getPropertyMethod =
+ GetStaticMethodIDOrDie(env, system, "getProperty",
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
// Java system properties that contain LayoutLib config. The initial values in the map
// are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 0000000..c17c480
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+ sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+ if (win != NULL) {
+ ANativeWindow_acquire(win.get());
+ }
+ return win.get();
+}
diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto
index 86261ec..9941b83 100644
--- a/core/proto/android/app/profilerinfo.proto
+++ b/core/proto/android/app/profilerinfo.proto
@@ -36,4 +36,5 @@
// Denotes an agent (and its parameters) to attach for profiling.
optional string agent = 6;
optional int32 clock_type = 7;
+ optional int32 profiler_output_version = 8;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5ae365c..f5bbbb4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -103,6 +103,7 @@
optional SettingProto accessibility_pinch_to_zoom_anywhere_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f55f3c7..76d7a41 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8424,9 +8424,11 @@
android:process=":ui">
</activity>
+ <!-- BlockedAppStreamingActivity is launched as the system user. -->
<activity android:name="com.android.internal.app.BlockedAppStreamingActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
+ android:showForAllUsers="true"
android:process=":ui">
</activity>
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..822aa22 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -14,185 +14,101 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
+ <!-- space -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
- <aapt:attr name="android:fillColor">
- <gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
- android:type="linear">
- <item android:offset="0" android:color="#FF073042"/>
- <item android:offset="1" android:color="#FF073042"/>
- </gradient>
- </aapt:attr>
- </path>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:fillColor="#202124"/>
<group>
<clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"/>
+ <!-- thrust plume -->
<path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
+ android:pathData="M253,153C249.82,187.48 225.67,262.17 167.98,285.04C110.3,307.92 73.96,318.12 63,320.36L256,399L449,320.36C438.04,318.12 401.7,307.92 344.02,285.04C286.33,262.17 262.18,187.48 259,153H256H253Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M253,153C251.5,187.42 241.7,261.98 214.5,284.82C187.3,307.65 170.17,317.84 165,320.08L256,398.58L347,320.08C341.83,317.84 324.7,307.65 297.5,284.82C270.3,261.98 260.5,187.42 259,153H256H253Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M256,153m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
+ android:fillColor="#ffffff"/>
+ <!-- android head and body -->
+ <path
+ android:pathData="M151,350h199v104h-199z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M358.42,350.44C358.36,350.02 358.29,349.6 358.22,349.18C357.8,346.6 357.27,344.04 356.65,341.52C355.57,337.12 354.21,332.82 352.59,328.66C351.22,325.13 349.66,321.7 347.93,318.38C345.7,314.11 343.18,310.01 340.41,306.11C337.01,301.34 333.21,296.86 329.06,292.74C327.32,291.01 325.52,289.34 323.65,287.73C319.62,284.26 315.32,281.09 310.78,278.26C310.82,278.19 310.85,278.12 310.89,278.05C312.97,274.46 315.05,270.88 317.13,267.29C319.17,263.78 321.2,260.28 323.23,256.77C324.69,254.26 326.15,251.74 327.61,249.22C327.95,248.62 328.22,248.01 328.43,247.37C329,245.61 329.02,243.76 328.57,242.03C328.45,241.61 328.31,241.19 328.14,240.78C327.97,240.38 327.77,239.98 327.54,239.6C326.76,238.29 325.65,237.16 324.26,236.33C323.02,235.6 321.64,235.16 320.23,235.03C319.64,234.98 319.04,234.99 318.45,235.05C317.96,235.1 317.47,235.19 316.99,235.32C315.26,235.77 313.67,236.72 312.42,238.08C311.98,238.57 311.58,239.12 311.23,239.71C309.77,242.23 308.31,244.75 306.85,247.27L300.76,257.78C298.68,261.37 296.6,264.96 294.52,268.55C294.29,268.94 294.06,269.33 293.83,269.73C293.52,269.6 293.21,269.48 292.89,269.36C281.43,264.99 269,262.6 256.01,262.6C255.65,262.6 255.3,262.6 254.94,262.6C243.39,262.72 232.29,264.73 221.93,268.33C220.73,268.75 219.55,269.19 218.38,269.65C218.16,269.29 217.95,268.92 217.74,268.55C215.66,264.96 213.58,261.38 211.5,257.79C209.47,254.28 207.43,250.78 205.4,247.27C203.94,244.76 202.48,242.23 201.02,239.72C200.68,239.12 200.28,238.58 199.83,238.09C198.59,236.72 196.99,235.78 195.27,235.32C194.79,235.2 194.3,235.1 193.81,235.05C193.22,234.99 192.62,234.99 192.03,235.04C190.61,235.16 189.23,235.6 188,236.34C186.6,237.16 185.5,238.3 184.71,239.6C184.49,239.99 184.29,240.38 184.12,240.79C183.95,241.2 183.8,241.61 183.69,242.04C183.23,243.76 183.26,245.62 183.82,247.38C184.03,248.01 184.3,248.63 184.65,249.23C186.11,251.74 187.57,254.26 189.02,256.78C191.06,260.28 193.09,263.79 195.12,267.29C197.2,270.88 199.28,274.47 201.36,278.06C201.38,278.09 201.4,278.12 201.41,278.15C197.22,280.76 193.23,283.64 189.47,286.8C187.21,288.69 185.04,290.68 182.96,292.75C178.81,296.87 175.01,301.35 171.6,306.12C168.82,310.02 166.31,314.11 164.09,318.39C162.35,321.71 160.79,325.14 159.42,328.67C157.8,332.83 156.44,337.13 155.36,341.53C154.75,344.05 154.22,346.6 153.79,349.19C153.72,349.61 153.66,350.03 153.59,350.45C153.36,351.95 153.16,353.46 153,354.98L359,354.98C358.84,353.46 358.64,351.95 358.41,350.45L358.42,350.44Z"
+ android:fillColor="#5F6368"/>
</group>
+ <!-- stars -->
<group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M131.04,134.34H127V138.38H131.04V134.34Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M167.04,256H163V260.04H167.04V256Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M373.49,127H369.45V131.04H373.49V127Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M292.04,226H288V230.04H292.04V226Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M319.04,186.91H315V190.95H319.04V186.91Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M355.04,222H351V226.04H355.04V222Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M192.04,136H188V140.04H192.04V136Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M336.08,196H328V204.08H336.08V196Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M222.04,212H218V216.04H222.04V212Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M163.08,175H155V183.08H163.08V175Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M211.08,143H203V151.08H211.08V143Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M369.08,204H361V212.08H369.08V204Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M169.21,204.34H161.13V212.42H169.21V204.34Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M383.04,160.07H374.95V168.15H383.04V160.07Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
+ android:pathData="M192.08,183H184V191.08H192.08V183Z"
+ android:fillColor="#ffffff"/>
</group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
+ <!-- patch frame -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:strokeWidth="55"
android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
+ android:strokeColor="#34A853"/>
+ <!-- text: ANDROID -->
<path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
- android:fillColor="#3ddc84"/>
+ android:pathData="M170.11,92.71C170.97,94.9 171.41,96 171.41,96C171.41,96 170.37,96 168.29,96C166.22,96 165.18,96 165.18,96C165.18,96 164.93,95.27 164.42,93.82L159.71,80.63L158.82,77.75H158.61L157.82,80.63L153.28,93.82C152.82,95.27 152.6,96 152.6,96C152.6,96 151.54,96 149.43,96C147.33,96 146.28,96 146.28,96C146.28,96 146.7,94.89 147.56,92.67L155.21,72.87C155.89,71.07 156.23,70.17 156.23,70.17C156.23,70.17 157.06,70.17 158.72,70.17C160.52,70.17 161.42,70.17 161.42,70.17C161.42,70.17 161.76,71.07 162.46,72.87L170.11,92.71ZM166.04,91.12H158.64V86.91H164.42L166.04,91.12ZM158.64,91.12H151.08L152.63,86.91H158.64V91.12ZM203.85,93.46C203.85,95.15 203.85,96 203.85,96C203.85,96 202.95,96 201.15,96C199.38,96 198.5,96 198.5,96C198.5,96 198.03,95.26 197.08,93.79L188.08,79.75H187.88L188.01,83.45V93.25C188.01,95.08 188.01,96 188.01,96C188.01,96 187.03,96 185.09,96C183.15,96 182.17,96 182.17,96C182.17,96 182.17,95.08 182.17,93.25L182.16,73.9C182.16,71.45 182.16,70.22 182.16,70.22C182.16,70.22 183.19,70.22 185.25,70.22C187.24,70.22 188.24,70.22 188.24,70.22C188.24,70.22 188.7,70.96 189.63,72.42L198.16,85.85H198.36L198.21,82.09V72.89C198.21,71.11 198.21,70.22 198.21,70.22C198.21,70.22 199.15,70.22 201.04,70.22C202.91,70.22 203.85,70.22 203.85,70.22C203.85,70.22 203.85,71.11 203.85,72.89V93.46ZM226.52,96H220.17C218.24,96 217.27,96 217.27,96C217.27,96 217.27,95.02 217.27,93.05V73.19C217.27,71.21 217.27,70.22 217.27,70.22C217.27,70.22 218.24,70.22 220.17,70.22H226.52C230.46,70.22 233.63,71.41 236.03,73.77C238.43,76.12 239.63,79.23 239.63,83.09C239.63,86.98 238.43,90.11 236.03,92.47C233.63,94.82 230.46,96 226.52,96ZM223.17,75.64V90.74H226.18C228.46,90.77 230.27,90.11 231.62,88.78C232.96,87.44 233.63,85.57 233.63,83.17C233.63,80.78 232.96,78.93 231.62,77.62C230.28,76.3 228.47,75.64 226.18,75.64H223.17ZM257.51,93.23C257.51,95.08 257.51,96 257.51,96C257.51,96 256.54,96 254.6,96C252.66,96 251.7,96 251.7,96C251.7,96 251.7,95.09 251.7,93.26V73.19C251.7,71.21 251.7,70.22 251.7,70.22C251.7,70.22 252.66,70.22 254.6,70.22H261.89C264.44,70.22 266.6,70.98 268.35,72.49C270.1,74 270.98,76.03 270.98,78.56C270.98,80.9 270.14,82.83 268.47,84.35C266.81,85.87 264.65,86.62 262.01,86.62H254.02V82.41H261.33C262.4,82.41 263.29,82.08 264.01,81.42C264.73,80.75 265.09,79.88 265.09,78.81C265.09,77.84 264.74,77.03 264.05,76.38C263.35,75.72 262.49,75.39 261.47,75.39H257.51V93.23ZM264.77,93.82L258.66,84.46L264.8,84.25L271.23,93.62C272.37,95.21 272.94,96 272.94,96C272.94,96 271.82,96 269.57,96C267.3,96 266.17,96 266.17,96C266.17,96 265.7,95.27 264.77,93.82ZM296.04,96.58C292.33,96.58 289.16,95.33 286.52,92.85C283.89,90.37 282.58,87.11 282.58,83.09C282.58,79.07 283.9,75.83 286.54,73.36C289.19,70.88 292.36,69.65 296.04,69.65C299.71,69.65 302.87,70.9 305.51,73.41C308.16,75.91 309.49,79.13 309.49,83.09C309.49,87.03 308.17,90.26 305.53,92.8C302.9,95.32 299.74,96.58 296.04,96.58ZM296.04,90.83C298.19,90.83 299.98,90.1 301.41,88.64C302.85,87.17 303.57,85.33 303.57,83.09C303.57,80.84 302.84,78.99 301.39,77.55C299.95,76.11 298.17,75.39 296.04,75.39C293.92,75.39 292.13,76.12 290.68,77.57C289.24,79.01 288.52,80.85 288.52,83.09C288.52,85.35 289.23,87.2 290.66,88.66C292.1,90.11 293.89,90.83 296.04,90.83ZM327.64,93.05C327.64,95.02 327.64,96 327.64,96C327.64,96 326.63,96 324.61,96C322.59,96 321.57,96 321.57,96C321.57,96 321.57,95.02 321.57,93.05V73.18C321.57,71.21 321.57,70.22 321.57,70.22C321.57,70.22 322.58,70.22 324.6,70.22C326.63,70.22 327.64,70.22 327.64,70.22C327.64,70.22 327.64,71.21 327.64,73.18V93.05ZM350.31,96H343.96C342.03,96 341.06,96 341.06,96C341.06,96 341.06,95.02 341.06,93.05V73.19C341.06,71.21 341.06,70.22 341.06,70.22C341.06,70.22 342.03,70.22 343.96,70.22H350.31C354.25,70.22 357.42,71.41 359.82,73.77C362.22,76.12 363.42,79.23 363.42,83.09C363.42,86.98 362.22,90.11 359.82,92.47C357.42,94.82 354.25,96 350.31,96ZM346.96,75.64V90.74H349.97C352.25,90.77 354.06,90.11 355.41,88.78C356.75,87.44 357.42,85.57 357.42,83.17C357.42,80.78 356.75,78.93 355.41,77.62C354.07,76.3 352.26,75.64 349.97,75.64H346.96Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- text: 15 -->
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
- android:fillColor="#fff"/>
+ android:pathData="M236.59,363.25C236.59,365.75 236.59,367 236.59,367C236.59,367 235.32,367 232.79,367C230.32,367 229.09,367 229.09,367C229.09,367 229.09,365.75 229.09,363.25V305.85L216.64,314.75C215,315.92 214.19,316.5 214.19,316.5C214.19,316.5 213.54,315.5 212.24,313.5C210.97,311.6 210.34,310.65 210.34,310.65C210.34,310.62 211.2,309.98 212.94,308.75L227.64,298.2C230.3,296.23 231.64,295.25 231.64,295.25C231.64,295.25 232.1,295.25 233.04,295.25C235.4,295.25 236.59,295.25 236.59,295.25C236.59,295.25 236.59,296.47 236.59,298.9V363.25ZM247.09,330L251.19,299C251.52,296.6 251.69,295.4 251.69,295.4C251.69,295.4 252.77,295.4 254.94,295.4H284.54C286.97,295.4 288.19,295.4 288.19,295.4C288.19,295.4 288.19,296.58 288.19,298.95C288.19,301.48 288.19,302.75 288.19,302.75C288.19,302.75 286.97,302.75 284.54,302.75H257.49L254.19,327.45L254.39,327.5C256.09,325.77 258.27,324.38 260.94,323.35C263.61,322.28 266.65,321.75 270.09,321.75C276.55,321.75 281.99,323.97 286.39,328.4C290.79,332.8 292.99,338.32 292.99,344.95C292.99,351.75 290.8,357.4 286.44,361.9C282.11,366.37 276.42,368.6 269.39,368.6C263.09,368.6 257.77,367 253.44,363.8C249.1,360.6 246.26,356.85 244.89,352.55C244.26,350.32 243.94,349.2 243.94,349.2C243.94,349.2 245.09,348.77 247.39,347.9C249.79,347 250.99,346.55 250.99,346.55C250.99,346.55 251.3,347.73 251.94,350.1C252.8,352.73 254.71,355.27 257.64,357.7C260.61,360.13 264.44,361.35 269.14,361.35C274.27,361.35 278.24,359.88 281.04,356.95C283.84,353.98 285.24,350.03 285.24,345.1C285.24,340.37 283.67,336.52 280.54,333.55C277.44,330.58 273.4,329.1 268.44,329.1C265.47,329.1 262.95,329.52 260.89,330.35C258.82,331.15 257.09,332.28 255.69,333.75C254.39,335.25 253.74,336 253.74,336C253.74,336 252.55,335.52 250.19,334.55C247.85,333.62 246.69,333.15 246.69,333.15C246.69,333.15 246.82,332.1 247.09,330Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- spacecraft -->
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
- android:fillColor="#fff"/>
+ android:pathData="M256.12,121C249.43,121 244,126.27 244,132.77V147.29C244,148.54 245.02,149.56 246.27,149.56C247.53,149.56 248.55,148.55 248.55,147.29V143.38C248.55,140.87 250.58,138.83 253.09,138.83H259.15C261.66,138.83 263.7,140.87 263.7,143.38V147.29C263.7,148.54 264.71,149.56 265.97,149.56C267.23,149.56 268.24,148.55 268.24,147.29V132.77C268.24,126.27 262.82,121 256.12,121H256.12Z"
+ android:fillColor="#E9F3EB"/>
</vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 53a6836..8ae2a9b 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2023 The Android Open Source Project
+Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,36 +19,20 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
<path
- android:name="ring"
- android:pathData="M 12 21 C 16.971 21 21 16.971 21 12 C 21 7.029 16.971 3 12 3 C 7.029 3 3 7.029 3 12 C 3 16.971 7.029 21 12 21 Z"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeWidth="2"/>
- <group android:name="group">
- <clip-path
- android:pathData="M 20.5 12 C 20.5 16.694 16.694 20.5 12 20.5 C 7.306 20.5 3.5 16.694 3.5 12 C 3.5 7.306 7.306 3.5 12 3.5 C 16.694 3.5 20.5 7.306 20.5 12 Z"/>
- <path
- android:pathData="M 14.812 9.023 C 13.014 9.707 11.027 9.707 9.229 9.023 L 8.265 10.693 C 8.06 11.048 7.605 11.17 7.25 10.964 C 6.895 10.759 6.773 10.305 6.978 9.949 L 7.899 8.355 C 5.988 7.137 4.702 5.084 4.502 2.695 L 4.456 2.153 L 19.584 2.153 L 19.539 2.695 C 19.339 5.084 18.052 7.137 16.142 8.355 L 17.067 9.958 C 17.259 10.307 17.142 10.746 16.801 10.952 C 16.45 11.165 15.993 11.052 15.781 10.701 L 15.775 10.693 L 14.812 9.023 Z"
- android:fillColor="#ffffff"/>
- <group android:name="stars">
- <path android:pathData="
- M 7,14 h1v1h-1z
- M 13,15 h1v1h-1z
- M 14,11 h1v1h-1z
-
- M 11,17 h0.5v0.5h-0.5z
- M 10,15 h0.5v0.5h-0.5z
- M 13,18 h0.5v0.5h-0.5z
- M 17,15 h0.5v0.5h-0.5z
- M 15,14 h0.5v0.5h-0.5z
- M 18,12 h0.5v0.5h-0.5z
- M 5,13 h0.5v0.5h-0.5z
- M 5,10 h0.5v0.5h-0.5z
- M 9,11 h0.5v0.5h-0.5z
- M 8,17 h0.5v0.5h-0.5z
- M 12,12 h0.5v0.5h-0.5z
- " android:fillColor="#ffffff"/>
- </group>
- </group>
+ android:pathData="M5,14.978h14v9.8h-14z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
</vector>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b262ebd..0df49dc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2300,7 +2300,7 @@
ensure that the status bar has enough contrast with the contents of this app, and set
an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceStatusBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2320,7 +2320,7 @@
ensure that the navigation bar has enough contrast with the contents of this app, and
set an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceNavigationBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2335,7 +2335,7 @@
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2431,7 +2431,9 @@
<!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
<p>
- Defaults to {@code default}.
+ Defaults to {@code default}. But if the window fills the screen, and it belongs to an app
+ targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or
+ above, the behavior will be the same as specifying {@code always} regardless.
<p>
See also
{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode
@@ -2528,8 +2530,8 @@
<!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
- <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
- app targets
+ <p>If this is false, the edge-to-edge enforcement will be applied to the window if it
+ belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
The affected behaviors are:
<ul>
@@ -2537,9 +2539,8 @@
through the {@link android.view.WindowInsets} to the content view, as if calling
{@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
<li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
- the non-floating windows will be set to {@link
+ the fill-screen windows will behave as specifying {@link
android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
- Changing it to other values will cause {@link lang.IllegalArgumentException}.
<li>The framework will set {@link android.R.attr#statusBarColor},
{@link android.R.attr#navigationBarColor}, and
{@link android.R.attr#navigationBarDividerColor} to transparent.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a622d36..1a61870 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3009,6 +3009,10 @@
<!-- Maximum number of users we allow to be running at a time -->
<integer name="config_multiuserMaxRunningUsers">3</integer>
+ <!-- Number of seconds of uptime after a full user enters the background before we attempt to
+ stop it due to inactivity. Set to -1 to disable scheduling stopping background users. -->
+ <integer name="config_backgroundUserScheduledStopTimeSecs">1800</integer> <!-- 30 minutes -->
+
<!-- Whether to delay user data locking for background user.
If false, user switched-out from user switching will still be in running state until
config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7c9f2ef..a248ede 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -235,6 +235,12 @@
<integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
<java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+ <!-- Reevaluate existing data networks for bootstrap sim data usage at mentioned intervals
+ during esim bootstrap activation. If the value set is 0 or -1, default interval of
+ 60000 millis will be set. -->
+ <integer name="config_reevaluate_bootstrap_sim_data_usage_millis">60000</integer>
+ <java-symbol type="integer" name="config_reevaluate_bootstrap_sim_data_usage_millis" />
+
<!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 461137c..f31d390 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5188,7 +5188,7 @@
<string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
- <string name="package_installed_device_owner">Installed by your admin</string>
+ <string name="package_installed_device_owner">Installed by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
<string name="package_updated_device_owner">Updated by your admin</string>
<!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
@@ -6457,16 +6457,4 @@
<string name="satellite_notification_how_it_works">How it works</string>
<!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
<string name="unarchival_session_app_label">Pending...</string>
-
- <!-- Background user sound notification related messages -->
- <!-- Notification title when sound comes from a call on background user [CHAR LIMIT=NOTIF_TITLE]-->
- <string name="bg_user_sound_notification_title_call">Call for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
- <!-- Notification title when sound comes from an alarm or timer on background user [CHAR LIMIT=NOTIF_TITLE]-->
- <string name="bg_user_sound_notification_title_alarm">Alarm for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
- <!-- Notification action button to prompt user switch to the background user [CHAR LIMIT=NONE] -->
- <string name="bg_user_sound_notification_button_switch_user">Switch user</string>
- <!-- Notification action button to mute the sound from the background user [CHAR LIMIT=NONE] -->
- <string name="bg_user_sound_notification_button_mute">Mute</string>
- <!-- Notification content action to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
- <string name="bg_user_sound_notification_message">Tap to mute sound</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 91d7ce0..ead5827 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -488,6 +488,7 @@
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
+ <java-symbol type="integer" name="config_backgroundUserScheduledStopTimeSecs" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
@@ -5380,13 +5381,6 @@
<java-symbol type="string" name="satellite_notification_how_it_works" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
- <!-- System notification for background user sound -->
- <java-symbol type="string" name="bg_user_sound_notification_title_call" />
- <java-symbol type="string" name="bg_user_sound_notification_title_alarm" />
- <java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
- <java-symbol type="string" name="bg_user_sound_notification_button_mute" />
- <java-symbol type="string" name="bg_user_sound_notification_message" />
-
<!-- DisplayManager configs. -->
<java-symbol type="bool" name="config_evenDimmerEnabled" />
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
index 89b3a7a..d41e981 100644
--- a/core/tests/FileSystemUtilsTest/TEST_MAPPING
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "FileSystemUtilsTests"
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d930e4d..3c042ba 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -78,6 +78,6 @@
public void testRestrictionLevel() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.restrictionLevelToName(
- ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
}
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 3a872b5..5bf88da 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -28,7 +28,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -97,7 +99,7 @@
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -210,6 +212,39 @@
verify(mFaceDetectionCallback).onDetectionError(anyInt());
}
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FaceManager.AuthenticationCallback authenticationCallback1 = mock(
+ FaceManager.AuthenticationCallback.class);
+ final FaceManager.AuthenticationCallback authenticationCallback2 = mock(
+ FaceManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ faceServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFaceServiceReceiver> faceServiceReceivers =
+ faceServiceReceiverArgumentCaptor.getAllValues();
+ faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
+
private void initializeProperties() throws RemoteException {
verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index ce7d6a9..c3ea7d3 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -26,7 +26,9 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -93,6 +95,7 @@
mHandler = new Handler(mLooper.getLooper());
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -187,4 +190,38 @@
verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
}
+
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FingerprintManager.AuthenticationCallback authenticationCallback1 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+ final FingerprintManager.AuthenticationCallback authenticationCallback2 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFingerprintServiceReceiver> fingerprintServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ fingerprintServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFingerprintServiceReceiver> fingerprintServiceReceivers =
+ fingerprintServiceReceiverArgumentCaptor.getAllValues();
+ fingerprintServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ fingerprintServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 80fef6c..ccebd03 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1258,18 +1258,6 @@
sInstrumentation.waitForIdleSync();
mViewRootImpl = mView.getViewRootImpl();
- waitForFrameRateCategoryToSettle(mView);
-
- sInstrumentation.runOnMainSync(() -> {
- assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
- mViewRootImpl.getPreferredFrameRateCategory());
- mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
- runAfterDraw(() -> assertEquals(expected,
- mViewRootImpl.getLastPreferredFrameRateCategory()));
- });
- waitForAfterDraw();
waitForFrameRateCategoryToSettle(mView);
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 82251b8..4921e4a 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,7 +20,6 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
@@ -82,14 +81,8 @@
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && !mPhoneWindow.isFloating()) {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
- } else {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
- }
+ assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+ is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 8d66cfc..1dbb775 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
@@ -49,6 +50,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class ActionBarOverlayLayoutTest {
private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0);
@@ -167,10 +169,67 @@
assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
}
+ @Test
+ public void topInset_cutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5)));
+ }
+
+ @Test
+ public void topInset_cutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
- false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
- null, null, 0, 0);
+ final Insets cutoutInsets = cutout != null
+ ? Insets.of(cutout.getSafeInsets())
+ : Insets.NONE;
+ return new WindowInsets.Builder()
+ .setSystemWindowInsets(content)
+ .setDisplayCutout(cutout)
+ .setInsets(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setInsetsIgnoringVisibility(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setVisible(WindowInsets.Type.displayCutout(), true)
+ .build();
}
private ViewGroup createViewGroupWithId(int id) {
@@ -181,14 +240,16 @@
static class TestActionBarOverlayLayout extends ActionBarOverlayLayout {
private boolean mStable;
+ private boolean mHasContentOnApplyWindowInsetsListener;
public TestActionBarOverlayLayout(Context context) {
super(context);
+ mHasContentOnApplyWindowInsetsListener = true;
}
@Override
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
- if (mStable) {
+ if (mStable || !hasContentOnApplyWindowInsetsListener()) {
// Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that
// without being attached to a window.
outLocalInsets.setEmpty();
@@ -202,6 +263,15 @@
setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0);
}
+ void setHasContentOnApplyWindowInsetsListener(boolean hasListener) {
+ mHasContentOnApplyWindowInsetsListener = hasListener;
+ }
+
+ @Override
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mHasContentOnApplyWindowInsetsListener;
+ }
+
@Override
public int getWindowSystemUiVisibility() {
return getSystemUiVisibility();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fc4277e..82d2381 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,6 +594,8 @@
<permission name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<permission name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <permission name="android.permission.SETUP_FSVERITY" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index a454d48..e829d4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,8 +17,10 @@
package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -182,6 +184,10 @@
mResizeTransition = null;
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
+
+ if (isRemovePipTransition(info)) {
+ return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
+ }
return false;
}
@@ -291,6 +297,10 @@
startOverlayFadeoutAnimation();
}
+ //
+ // Subroutines setting up and starting transitions' animations.
+ //
+
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -326,6 +336,7 @@
mPipScheduler.setPipTaskToken(mPipTaskToken);
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
return true;
}
@@ -353,11 +364,26 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
onExitPip();
return true;
}
+ private boolean removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+
+ //
+ // Utility methods for checking PiP-related transition info and requests.
+ //
+
@Nullable
private TransitionInfo.Change getPipChange(TransitionInfo info) {
for (TransitionInfo.Change change : info.getChanges()) {
@@ -415,6 +441,25 @@
&& info.getChanges().size() == 1;
}
+ private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
+ if (mPipTaskToken == null) {
+ // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
+ return false;
+ }
+ TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ if (pipChange == null) {
+ // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
+ return false;
+ }
+
+ boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK
+ && pipChange.getMode() == TRANSIT_TO_BACK;
+ boolean isPipClosed = info.getType() == TRANSIT_CLOSE
+ && pipChange.getMode() == TRANSIT_CLOSE;
+ // PiP is being removed if the pinned task is either moved to back or closed.
+ return isPipMovedToBack || isPipClosed;
+ }
+
/**
* TODO: b/275910498 Use a new implementation of the PiP animator here.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index eebd133..77b8663 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.recents;
+import android.annotation.Nullable;
+import android.graphics.Color;
+
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -40,4 +43,12 @@
*/
default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
}
+
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ default void setTransitionBackgroundColor(@Nullable Color color) {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0c99aed..e7d9812 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -30,6 +30,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Color;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -476,6 +477,16 @@
});
});
}
+
+ @Override
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mMainExecutor.execute(() -> {
+ if (mTransitionHandler == null) {
+ return;
+ }
+ mTransitionHandler.setTransitionBackgroundColor(color);
+ });
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 24cf370..c625b69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -36,6 +36,7 @@
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.content.Intent;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -56,6 +57,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
@@ -92,6 +95,7 @@
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
private final HomeTransitionObserver mHomeTransitionObserver;
+ private @Nullable Color mBackgroundColor;
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
@Nullable RecentTasksController recentTasksController,
@@ -123,6 +127,15 @@
mStateListeners.add(listener);
}
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mBackgroundColor = color;
+ }
+
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -469,6 +482,16 @@
final int belowLayers = info.getChanges().size();
final int middleLayers = info.getChanges().size() * 2;
final int aboveLayers = info.getChanges().size() * 3;
+
+ // Add a background color to each transition root in this transition.
+ if (mBackgroundColor != null) {
+ info.getChanges().stream()
+ .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
+ .distinct()
+ .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
+ .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
+ }
+
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -1107,6 +1130,29 @@
return true;
}
+ private void createBackgroundSurface(SurfaceControl.Transaction transaction,
+ SurfaceControl parent, int layer) {
+ if (mBackgroundColor == null) {
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding background color to layer=%d", layer);
+ final SurfaceControl background = new SurfaceControl.Builder()
+ .setName("recents_background")
+ .setColorLayer()
+ .setOpaque(true)
+ .setParent(parent)
+ .build();
+ transaction.setColor(background, colorToFloatArray(mBackgroundColor));
+ transaction.setLayer(background, layer);
+ transaction.setAlpha(background, 1F);
+ transaction.show(background);
+ }
+
+ private static float[] colorToFloatArray(@NonNull Color color) {
+ return new float[]{color.red(), color.green(), color.blue()};
+ }
+
private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
if (!sendUserLeaveHint && task.isLeaf()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 2bbe530..da1699c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -467,8 +467,8 @@
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mTaskSurface,
- mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo,
+ mTaskSurface, mSurfaceControlTransactionSupplier);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index d072f8c..2c4092a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.ColorRes;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
@@ -40,7 +41,7 @@
import android.window.TaskConstants;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.DisplayController;
import java.util.function.Supplier;
@@ -48,6 +49,7 @@
* Creates and updates a veil that covers task contents on resize.
*/
public class ResizeVeil {
+ private static final String TAG = "ResizeVeil";
private static final int RESIZE_ALPHA_DURATION = 100;
private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL;
@@ -57,8 +59,10 @@
private static final int VEIL_ICON_LAYER = 1;
private final Context mContext;
- private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+ private final DisplayController mDisplayController;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
+ private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory;
+ private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Drawable mAppIcon;
private ImageView mIconView;
@@ -74,41 +78,82 @@
private final RunningTaskInfo mTaskInfo;
private SurfaceControlViewHost mViewHost;
- private final Display mDisplay;
+ private Display mDisplay;
private ValueAnimator mVeilAnimator;
- public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
+ private boolean mIsShowing = false;
+
+ private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (mTaskInfo.displayId != displayId) {
+ return;
+ }
+ mDisplayController.removeDisplayWindowListener(this);
+ setupResizeVeil();
+ }
+ };
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ this(context,
+ displayController,
+ appIcon,
+ taskInfo,
+ taskSurface,
+ surfaceControlTransactionSupplier,
+ new SurfaceControlBuilderFactory() {},
+ new WindowDecoration.SurfaceControlViewHostFactory() {});
+ }
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ SurfaceControlBuilderFactory surfaceControlBuilderFactory,
+ WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
+ mDisplayController = displayController;
mAppIcon = appIcon;
- mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mTaskInfo = taskInfo;
mParentSurface = taskSurface;
- mDisplay = display;
+ mSurfaceControlBuilderFactory = surfaceControlBuilderFactory;
+ mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
setupResizeVeil();
}
-
/**
* Create the veil in its default invisible state.
*/
private void setupResizeVeil() {
- mVeilSurface = mSurfaceControlBuilderSupplier.get()
+ if (!obtainDisplayOrRegisterListener()) {
+ // Display may not be available yet, skip this until then.
+ return;
+ }
+ mVeilSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + mTaskInfo.taskId)
.setContainerLayer()
- .setName("Resize veil of Task=" + mTaskInfo.taskId)
.setHidden(true)
.setParent(mParentSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
- mBackgroundSurface = SurfaceUtils.makeColorLayer(mVeilSurface,
- "Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession);
- mIconSurface = mSurfaceControlBuilderSupplier.get()
- .setName("Resize veil icon of Task= " + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mVeilSurface)
+ mBackgroundSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession)
+ .setColorLayer()
.setHidden(true)
+ .setParent(mVeilSurface)
+ .setCallsite("ResizeVeil#setupResizeVeil")
+ .build();
+ mIconSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setHidden(true)
+ .setParent(mVeilSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
@@ -131,10 +176,20 @@
final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration,
mIconSurface, null /* hostInputToken */);
- mViewHost = new SurfaceControlViewHost(mContext, mDisplay, wwm, "ResizeVeil");
+
+ mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil");
mViewHost.setView(root, lp);
}
+ private boolean obtainDisplayOrRegisterListener() {
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ if (mDisplay == null) {
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ return false;
+ }
+ return true;
+ }
+
/**
* Shows the veil surface/view.
*
@@ -146,6 +201,12 @@
*/
public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
Rect taskBounds, boolean fadeIn) {
+ if (!isReady() || isVisible()) {
+ t.apply();
+ return;
+ }
+ mIsShowing = true;
+
// Parent surface can change, ensure it is up to date.
if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface);
@@ -226,6 +287,9 @@
* Animate veil's alpha to 1, fading it in.
*/
public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ if (!isReady() || isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
}
@@ -247,6 +311,9 @@
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(Rect newBounds) {
+ if (!isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
updateResizeVeil(t, newBounds);
}
@@ -260,6 +327,10 @@
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+ if (!isVisible()) {
+ t.apply();
+ return;
+ }
if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
mVeilAnimator.removeAllUpdateListeners();
mVeilAnimator.end();
@@ -272,6 +343,9 @@
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
+ if (!isVisible()) {
+ return;
+ }
cancelAnimation();
mVeilAnimator = new ValueAnimator();
mVeilAnimator.setFloatValues(1, 0);
@@ -292,6 +366,7 @@
}
});
mVeilAnimator.start();
+ mIsShowing = false;
}
@ColorRes
@@ -318,10 +393,26 @@
}
/**
+ * Whether the resize veil is currently visible.
+ *
+ * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon
+ * as the animation starts.
+ */
+ private boolean isVisible() {
+ return mIsShowing;
+ }
+
+ /** Whether the resize veil is ready to be shown. */
+ private boolean isReady() {
+ return mViewHost != null;
+ }
+
+ /**
* Dispose of veil when it is no longer needed, likely on close of its container decor.
*/
void dispose() {
cancelAnimation();
+ mIsShowing = false;
mVeilAnimator = null;
if (mViewHost != null) {
@@ -342,5 +433,16 @@
mVeilSurface = null;
}
t.apply();
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ }
+
+ interface SurfaceControlBuilderFactory {
+ default SurfaceControl.Builder create(@NonNull String name) {
+ return new SurfaceControl.Builder().setName(name);
+ }
+ default SurfaceControl.Builder create(@NonNull String name,
+ @NonNull SurfaceSession surfaceSession) {
+ return new SurfaceControl.Builder(surfaceSession).setName(name);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 51b0a24..36da1ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -668,6 +668,10 @@
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
+ default SurfaceControlViewHost create(Context c, Display d,
+ WindowlessWindowManager wmm, String callsite) {
+ return new SurfaceControlViewHost(c, d, wmm, callsite);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS
new file mode 100644
index 0000000..73a5a23
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS
@@ -0,0 +1,5 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Freeform
+# Bug component: 929241
+
+uysalorhan@google.com
+pragyabajoria@google.com
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
new file mode 100644
index 0000000..4c781d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.extractors.ITransitionMatcher
+import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.traces.wm.Transition
+import android.tools.traces.wm.TransitionType
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithDragLandscape : EnterDesktopWithDrag(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() =
+ super.enterDesktopWithDrag()
+
+ companion object {
+ private val END_DRAG_TO_DESKTOP = FlickerConfigEntry(
+ scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
+ extractor = ShellTransitionScenarioExtractor(
+ transitionMatcher = object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP}
+ }
+ }),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(END_DRAG_TO_DESKTOP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
new file mode 100644
index 0000000..d99d875
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.extractors.ITransitionMatcher
+import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.traces.wm.Transition
+import android.tools.traces.wm.TransitionType
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithDragPortrait : EnterDesktopWithDrag(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() =
+ super.enterDesktopWithDrag()
+
+ companion object {
+ private val END_DRAG_TO_DESKTOP = FlickerConfigEntry(
+ scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
+ extractor = ShellTransitionScenarioExtractor(
+ transitionMatcher = object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP}
+ }
+ }),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(END_DRAG_TO_DESKTOP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
new file mode 100644
index 0000000..0403b4f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.flicker.utils.DesktopModeUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterDesktopWithDrag
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ }
+
+ @Test
+ open fun enterDesktopWithDrag() {
+ DesktopModeUtils.enterDesktopWithDrag(wmHelper, device, testApp)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt
new file mode 100644
index 0000000..345bc5e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.flicker.utils
+
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.helpers.SYSTEMUI_PACKAGE
+import android.tools.traces.component.IComponentMatcher
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.wm.WindowingMode
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+
+/**
+ * Provides a collection of utility functions for desktop mode testing.
+ */
+object DesktopModeUtils {
+ private const val TIMEOUT_MS = 3_000L
+ private const val CAPTION = "desktop_mode_caption"
+ private const val CAPTION_HANDLE = "caption_handle"
+ private const val MAXIMIZE_BUTTON = "maximize_button_view"
+
+ private val captionFullscreen: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ private val captionHandle: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, CAPTION_HANDLE)
+ private val maximizeButton: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, MAXIMIZE_BUTTON)
+
+ /**
+ * Wait for an app moved to desktop to finish its transition.
+ */
+ private fun waitForAppToMoveToDesktop(
+ wmHelper: WindowManagerStateHelper,
+ currentApp: IComponentMatcher,
+ ) {
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceAppeared(currentApp)
+ .withFreeformApp(currentApp)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ /**
+ * Click maximise button on the app header for the given app.
+ */
+ fun maximiseDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ currentApp: StandardAppHelper
+ ) {
+ if (wmHelper.getWindow(currentApp)?.windowingMode
+ != WindowingMode.WINDOWING_MODE_FREEFORM.value)
+ error("expected a freeform window to maximise but window is not in freefrom mode")
+
+ val maximizeButton =
+ device.wait(Until.findObject(maximizeButton), TIMEOUT_MS)
+ ?: error("Unable to find view $maximizeButton\n")
+ maximizeButton.click()
+ }
+
+ /**
+ * Move an app to Desktop by dragging the app handle at the top.
+ */
+ fun enterDesktopWithDrag(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ currentApp: StandardAppHelper,
+ ) {
+ currentApp.launchViaIntent(wmHelper)
+ dragToDesktop(wmHelper, currentApp, device)
+ waitForAppToMoveToDesktop(wmHelper, currentApp)
+ }
+
+ private fun dragToDesktop(
+ wmHelper: WindowManagerStateHelper,
+ currentApp: StandardAppHelper,
+ device: UiDevice
+ ) {
+ val windowRect = wmHelper.getWindowRegion(currentApp).bounds
+ val startX = windowRect.centerX()
+
+ // Start dragging a little under the top to prevent dragging the notification shade.
+ val startY = 10
+
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
+ // The position we want to drag to
+ val endY = displayRect.centerY() / 2
+
+ // drag the window to move to desktop
+ device.drag(startX, startY, startX, endY, 100)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
new file mode 100644
index 0000000..847c2dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.windowdecor
+
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Tests for [ResizeVeil].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ResizeVeilTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ResizeVeilTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockAppIcon: Drawable
+ @Mock
+ private lateinit var mockDisplay: Display
+ @Mock
+ private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+ @Mock
+ private lateinit var mockSurfaceControlBuilderFactory: ResizeVeil.SurfaceControlBuilderFactory
+ @Mock
+ private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
+ @Spy
+ private val spyResizeVeilSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockResizeVeilSurface: SurfaceControl
+ @Spy
+ private val spyBackgroundSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockBackgroundSurface: SurfaceControl
+ @Spy
+ private val spyIconSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockIconSurface: SurfaceControl
+ @Mock
+ private lateinit var mockTransaction: SurfaceControl.Transaction
+
+ private val taskInfo = TestRunningTaskInfoBuilder().build()
+
+ @Before
+ fun setUp() {
+ whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any(), any()))
+ .thenReturn(mockSurfaceControlViewHost)
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + taskInfo.taskId))
+ .thenReturn(spyResizeVeilSurfaceBuilder)
+ doReturn(mockResizeVeilSurface).whenever(spyResizeVeilSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create(eq("Resize veil background of Task=" + taskInfo.taskId), any()))
+ .thenReturn(spyBackgroundSurfaceBuilder)
+ doReturn(mockBackgroundSurface).whenever(spyBackgroundSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + taskInfo.taskId))
+ .thenReturn(spyIconSurfaceBuilder)
+ doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build()
+ }
+
+ @Test
+ fun init_displayAvailable_viewHostCreated() {
+ createResizeVeil(withDisplayAvailable = true)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ }
+
+ @Test
+ fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() {
+ createResizeVeil(withDisplayAvailable = false)
+
+ verify(mockSurfaceControlViewHostFactory, never())
+ .create(any(), eq(mockDisplay), any<WindowlessWindowManager>(), eq("ResizeVeil"))
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(mockDisplayController).addDisplayWindowListener(captor.capture())
+
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId)).thenReturn(mockDisplay)
+ captor.value.onDisplayAdded(taskInfo.displayId)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun dispose_removesDisplayWindowListener() {
+ createResizeVeil().dispose()
+
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun showVeil() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).show(mockResizeVeilSurface)
+ verify(tx).show(mockBackgroundSurface)
+ verify(tx).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_displayUnavailable_doesNotShow() {
+ val veil = createResizeVeil(withDisplayAvailable = false)
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, never()).show(mockResizeVeilSurface)
+ verify(tx, never()).show(mockBackgroundSurface)
+ verify(tx, never()).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_alreadyVisible_doesNotShowAgain() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, times(1)).show(mockResizeVeilSurface)
+ verify(tx, times(1)).show(mockBackgroundSurface)
+ verify(tx, times(1)).show(mockIconSurface)
+ verify(tx, times(2)).apply()
+ }
+
+ @Test
+ fun showVeil_reparentsVeilToNewParent() {
+ val veil = createResizeVeil(parent = mock())
+ val tx = mock<SurfaceControl.Transaction>()
+
+ val newParent = mock<SurfaceControl>()
+ veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).reparent(mockResizeVeilSurface, newParent)
+ }
+
+ @Test
+ fun hideVeil_alreadyHidden_doesNothing() {
+ val veil = createResizeVeil()
+
+ veil.hideVeil()
+
+ verifyZeroInteractions(mockTransaction)
+ }
+
+ private fun createResizeVeil(
+ withDisplayAvailable: Boolean = true,
+ parent: SurfaceControl = mock()
+ ): ResizeVeil {
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId))
+ .thenReturn(if (withDisplayAvailable) mockDisplay else null)
+ return ResizeVeil(
+ context,
+ mockDisplayController,
+ mockAppIcon,
+ taskInfo,
+ parent,
+ { mockTransaction },
+ mockSurfaceControlBuilderFactory,
+ mockSurfaceControlViewHostFactory
+ )
+ }
+}
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931..36d8fba 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@
virtual void destroy() {}
+ int getBuffersDataSpace() { return 0; }
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc..753a699 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_android_core_graphics_stack",
default_applicable_licenses: ["frameworks_base_libs_hwui_license"],
}
@@ -93,6 +94,7 @@
host: {
include_dirs: [
"external/vulkan-headers/include",
+ "frameworks/av/media/ndk/include",
],
cflags: [
"-Wno-unused-variable",
@@ -142,7 +144,6 @@
"libsync",
"libui",
"aconfig_text_flags_c_lib",
- "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -267,6 +268,7 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "apex/android_canvas.cpp",
"apex/android_matrix.cpp",
"apex/android_paint.cpp",
"apex/android_region.cpp",
@@ -279,7 +281,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"apex/android_bitmap.cpp",
- "apex/android_canvas.cpp",
"apex/jni_runtime.cpp",
],
},
@@ -338,6 +339,8 @@
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
+ "jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/android_graphics_HardwareRendererObserver.cpp",
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
@@ -422,8 +425,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"jni/android_graphics_TextureLayer.cpp",
- "jni/android_graphics_HardwareRenderer.cpp",
- "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
@@ -448,6 +449,12 @@
"libstatssocket_lazy",
],
},
+ linux: {
+ srcs: ["platform/linux/utils/SharedLib.cpp"],
+ },
+ darwin: {
+ srcs: ["platform/darwin/utils/SharedLib.cpp"],
+ },
host: {
cflags: [
"-Wno-unused-const-variable",
@@ -543,6 +550,7 @@
"renderthread/CanvasContext.cpp",
"renderthread/DrawFrameTask.cpp",
"renderthread/Frame.cpp",
+ "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
@@ -576,6 +584,7 @@
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "Layer.cpp",
"LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
@@ -624,7 +633,6 @@
"renderthread/CacheManager.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
- "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
"renderthread/RenderThread.cpp",
@@ -635,7 +643,6 @@
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
- "Layer.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771..d3b48d3 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
#include "HardwareBufferHelpers.h"
+#ifdef __ANDROID__
#include <dlfcn.h>
+#endif
#include <log/log.h>
#ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c..df9f830 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
+#ifdef __ANDROID__
#include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
#include <SkStream.h>
#include <SkTypeface.h>
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
@@ -39,8 +42,10 @@
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
#include <pipeline/skia/ShaderCache.h>
#include <private/EGL/cache.h>
+#endif
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
#include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
namespace android {
@@ -498,7 +504,11 @@
return sk_ref_sp(img);
}
bm.setImmutable();
+#ifdef __ANDROID__
return SkImages::PinnableRasterFromBitmap(bm);
+#else
+ return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
}
return sk_ref_sp(img);
}
@@ -713,6 +723,7 @@
static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
if (jwidth <= 0 || jheight <= 0) {
ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@
sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+ return nullptr;
+#endif
}
static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@
const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
}
static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@
gCopyRequest.getDestinationBitmap =
GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+ void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+ void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+ fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
"Failed to find required symbol ANativeWindow_fromSurface!");
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 0000000..6e9f0b4
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 0000000..a9acf37
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 0000000..f4dcf0f
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+ static void* openSharedLib(std::string filename);
+ static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // SHAREDLIB_H
diff --git a/location/Android.bp b/location/Android.bp
index eb7cd01..5ba35ac 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -26,6 +26,7 @@
"com.android.internal.location",
],
libs: [
+ "android.location.flags-aconfig-java",
"app-compat-annotations",
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
],
diff --git a/location/api/current.txt b/location/api/current.txt
index 85e9f65..61afd26 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,8 +412,8 @@
field public static final int TYPE_GPS_L1CA = 257; // 0x101
field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
- field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
- field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
+ field @FlaggedApi("android.location.flags.gnss_api_navic_l1") public static final int TYPE_IRN_L1 = 1795; // 0x703
+ field @FlaggedApi("android.location.flags.gnss_api_navic_l1") public static final int TYPE_IRN_L5 = 1794; // 0x702
field public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
@@ -682,7 +682,7 @@
public final class AltitudeConverter {
ctor public AltitudeConverter();
method @WorkerThread public void addMslAltitudeToLocation(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
- method @FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL) public boolean tryAddMslAltitudeToLocation(@NonNull android.location.Location);
+ method @FlaggedApi("android.location.flags.geoid_heights_via_altitude_hal") public boolean tryAddMslAltitudeToLocation(@NonNull android.location.Location);
}
}
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 254d74a..f6e76a2 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -113,13 +113,13 @@
}
public final class GnssMeasurementRequest implements android.os.Parcelable {
- method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull public android.os.WorkSource getWorkSource();
+ method @FlaggedApi("android.location.flags.gnss_api_measurement_request_work_source") @NonNull public android.os.WorkSource getWorkSource();
method public boolean isCorrelationVectorOutputsEnabled();
}
public static final class GnssMeasurementRequest.Builder {
method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean);
- method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
+ method @FlaggedApi("android.location.flags.gnss_api_measurement_request_work_source") @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
}
public final class GnssReflectingPlane implements android.os.Parcelable {
@@ -591,7 +591,7 @@
package android.location.provider {
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
+ @FlaggedApi("android.location.flags.new_geocoder") public final class ForwardGeocodeRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getCallingAttributionTag();
method @NonNull public String getCallingPackage();
@@ -613,7 +613,7 @@
method @NonNull public android.location.provider.ForwardGeocodeRequest.Builder setCallingAttributionTag(@NonNull String);
}
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase {
+ @FlaggedApi("android.location.flags.new_geocoder") public abstract class GeocodeProviderBase {
ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String);
method @NonNull public final android.os.IBinder getBinder();
method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
@@ -672,7 +672,7 @@
method public void onProviderRequestChanged(@NonNull String, @NonNull android.location.provider.ProviderRequest);
}
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ReverseGeocodeRequest implements android.os.Parcelable {
+ @FlaggedApi("android.location.flags.new_geocoder") public final class ReverseGeocodeRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getCallingAttributionTag();
method @NonNull public String getCallingPackage();
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 19e59a7..d6d4989 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -79,6 +79,13 @@
}
flag {
+ name: "subscriptions_listener_thread"
+ namespace: "location"
+ description: "Flag for running onSubscriptionsChangeListener on FgThread"
+ bug: "332451908"
+}
+
+flag {
name: "gnss_configuration_from_resource"
namespace: "location"
description: "Flag for GNSS configuration from resource"
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ce7474c..3ba0d59 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5232,6 +5232,13 @@
setParameters(keys, values);
}
+ private void logAndRun(String message, Runnable r) {
+ final String TAG = "MediaCodec";
+ android.util.Log.d(TAG, "enter: " + message);
+ r.run();
+ android.util.Log.d(TAG, "exit : " + message);
+ }
+
/**
* Sets an asynchronous callback for actionable MediaCodec events.
*
@@ -5261,14 +5268,40 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[new handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[new handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
mCallbackHandler = newHandler;
}
}
} else if (mCallbackHandler != null) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[null handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[null handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
}
if (mCallbackHandler != null) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 5331046..6593533 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -205,6 +205,10 @@
* media container format specified by mimeType at the requested
* security level.
*
+ * Calling this method while the application is running on the physical Android device or a
+ * {@link android.companion.virtual.VirtualDevice} may lead to different results, based on
+ * the different DRM capabilities of the devices.
+ *
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
* or "video/webm"
@@ -1400,6 +1404,10 @@
* Open a new session with the MediaDrm object. A session ID is returned.
* By default, sessions are opened at the native security level of the device.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
*/
@@ -1422,6 +1430,10 @@
* can be queried using {@link #getSecurityLevel}. A session
* ID is returned.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @param level the new security level
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
@@ -2180,6 +2192,11 @@
* Returns a value that may be passed as a parameter to {@link #openSession(int)}
* requesting that the session be opened at the maximum security level of
* the device.
+ *
+ * This security level is only valid for the application running on the physical Android
+ * device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a
+ * {@link android.companion.virtual.VirtualDevice} the maximum supported security level
+ * might be different.
*/
public static final int getMaxSecurityLevel() {
return SECURITY_LEVEL_MAX;
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45b4370..988b5cf 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -332,7 +332,8 @@
<pre class=prettyprint>
<service android:name="<strong>MySynthDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.midi.MidiDeviceService" />
</intent-filter>
@@ -474,7 +475,8 @@
<pre class=prettyprint>
<service android:name="<strong>MidiEchoDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.midi.MidiUmpDeviceService" />
</intent-filter>
@@ -509,6 +511,11 @@
import android.media.midi.MidiReceiver;
import android.media.midi.MidiUmpDeviceService;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
public class MidiEchoDeviceService extends MidiUmpDeviceService {
private static final String TAG = "MidiEchoDeviceService";
// Other apps will write to this port.
@@ -528,8 +535,8 @@
@Override
// Declare the receivers associated with your input ports.
- public List<MidiReceiver> onGetInputPortReceivers() {
- return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+ public List<MidiReceiver> onGetInputPortReceivers() {
+ return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
}
/**
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 47637b8..4bacbf9 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,10 +15,7 @@
*/
package android.media.session;
-import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
-
import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -298,8 +295,9 @@
* foreground.
*
* @see Builder#setState
+ * @hide
*/
- @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+ // TODO: b/335561702 Unhide this symbol for the next API bump.
public static final int STATE_PLAYBACK_SUPPRESSED = 12;
/**
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce79..8609c4d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -82,6 +82,7 @@
"libhidlbase",
"libsonivox",
"server_configurable_flags",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hardware.drm@1.3",
@@ -100,6 +101,7 @@
static_libs: [
"libgrallocusage",
"libmedia_midiiowrapper",
+ "android.companion.virtualdevice.flags-aconfig-cc",
"android.media.playback.flags-aconfig-cc",
],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1c25080..48cd53d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -27,6 +27,8 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <android_companion_virtualdevice_flags.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
#include <android/hardware/drm/1.3/IDrmFactory.h>
#include <binder/Parcel.h>
#include <binder/PersistableBundle.h>
@@ -41,8 +43,10 @@
#include <map>
#include <string>
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
using ::android::os::PersistableBundle;
namespace drm = ::android::hardware::drm;
+namespace virtualdevice_flags = android::companion::virtualdevice::flags;
namespace android {
@@ -1045,6 +1049,26 @@
return level;
}
+std::vector<int> getVirtualDeviceIds() {
+ if (!virtualdevice_flags::device_aware_drm()) {
+ ALOGW("Device-aware DRM flag disabled.");
+ return std::vector<int>();
+ }
+
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("virtualdevice_native"));
+ if (binder != nullptr) {
+ auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+ std::vector<int> deviceIds;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ vdm->getDeviceIdsForUid(uid, &deviceIds);
+ return deviceIds;
+ } else {
+ ALOGW("Cannot get virtualdevice_native service");
+ return std::vector<int>();
+ }
+}
+
static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
sp<IDrm> drm = android::DrmUtils::MakeDrm();
if (drm == NULL) return env->NewByteArray(0);
@@ -1081,6 +1105,15 @@
}
DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (securityLevel > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ return false;
+ }
+ }
+
bool isSupported;
status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType,
securityLevel, &isSupported);
@@ -1106,6 +1139,16 @@
return NULL;
}
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (level == DrmPlugin::kSecurityLevelMax ||
+ level > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+ }
+ }
+
DrmStatus err = drm->openSession(level, sessionId);
if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) {
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index a64e3f2..36cba2d 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -19,6 +19,8 @@
#include "jni.h"
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
#include <media/stagefright/foundation/ABase.h>
#include <mediadrm/IDrm.h>
#include <mediadrm/IDrmClient.h>
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 97dfba1..8fe771c 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -41,7 +41,7 @@
android:supportsRtl="true">
<activity
- android:name=".CompanionDeviceActivity"
+ android:name=".CompanionAssociationActivity"
android:exported="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
similarity index 88%
rename from packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
rename to packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 1231b63..bf81d3f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -65,7 +65,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.Spanned;
-import android.util.Log;
+import android.util.Slog;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -91,9 +91,8 @@
* nearby devices to be associated with.
*/
@SuppressLint("LongLogTag")
-public class CompanionDeviceActivity extends FragmentActivity implements
+public class CompanionAssociationActivity extends FragmentActivity implements
CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CDM_CompanionDeviceActivity";
// Keep the following constants in sync with
@@ -183,11 +182,11 @@
@Override
public void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate()");
- boolean forceCancelDialog = getIntent().getBooleanExtra("cancel_confirmation", false);
+ boolean forceCancelDialog = getIntent().getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION,
+ false);
// Must handle the force cancel request in onNewIntent.
if (forceCancelDialog) {
- Log.i(TAG, "The confirmation does not exist, skipping the cancel request");
+ Slog.i(TAG, "The confirmation does not exist, skipping the cancel request");
finish();
}
@@ -198,13 +197,13 @@
@Override
protected void onStart() {
super.onStart();
- if (DEBUG) Log.d(TAG, "onStart()");
final Intent intent = getIntent();
- mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+ mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST, AssociationRequest.class);
mAppCallback = IAssociationRequestCallback.Stub.asInterface(
intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
- mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
+ mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER,
+ ResultReceiver.class);
requireNonNull(mRequest);
requireNonNull(mAppCallback);
@@ -221,29 +220,22 @@
initUI();
}
- @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
- protected void onNewIntent(Intent intent) {
+ protected void onNewIntent(@NonNull Intent intent) {
+ super.onNewIntent(intent);
+
// Force cancels the CDM dialog if this activity receives another intent with
// EXTRA_FORCE_CANCEL_CONFIRMATION.
boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
-
if (forCancelDialog) {
- Log.i(TAG, "Cancelling the user confirmation");
-
- cancel(/* discoveryTimeOut */ false,
- /* userRejected */ false, /* internalError */ false);
+ Slog.i(TAG, "Cancelling the user confirmation");
+ cancel(/* discoveryTimeOut */ false, /* userRejected */ false,
+ /* internalError */ false);
return;
}
// Handle another incoming request (while we are not done with the original - mRequest -
- // yet).
- final AssociationRequest request = requireNonNull(
- intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
-
- if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
-
- // We can only "process" one request at a time.
+ // yet). We can only "process" one request at a time.
final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
.asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
try {
@@ -255,7 +247,6 @@
@Override
protected void onStop() {
super.onStop();
- if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
// TODO: handle config changes without cancelling.
if (!isDone()) {
@@ -264,26 +255,8 @@
}
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (DEBUG) Log.d(TAG, "onDestroy()");
- }
-
- @Override
- public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed()");
- super.onBackPressed();
- }
-
- @Override
- public void finish() {
- if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump"));
- super.finish();
- }
-
private void initUI() {
- if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
+ Slog.d(TAG, "initUI(), request=" + mRequest);
final String packageName = mRequest.getPackageName();
final int userId = mRequest.getUserId();
@@ -292,7 +265,7 @@
try {
appLabel = getApplicationLabel(this, packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
+ Slog.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
CompanionDeviceDiscoveryService.stop(this);
setResultAndFinish(null, RESULT_INTERNAL_ERROR);
@@ -341,9 +314,9 @@
if (mRequest.isSelfManaged()) {
initUiForSelfManagedAssociation();
} else if (mRequest.isSingleDevice()) {
- initUiForSingleDevice(appLabel);
+ initUiForSingleDevice();
} else {
- initUiForMultipleDevices(appLabel);
+ initUiForMultipleDevices();
}
}
@@ -364,12 +337,12 @@
private void onAssociationApproved(@Nullable MacAddress macAddress) {
if (isDone()) {
- if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
}
mApproved = true;
- if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+ Slog.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
if (!mRequest.isSelfManaged()) {
requireNonNull(macAddress);
@@ -390,17 +363,8 @@
}
private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
- if (DEBUG) {
- Log.i(TAG, "cancel(), discoveryTimeout="
- + discoveryTimeout
- + ", userRejected="
- + userRejected
- + ", internalError="
- + internalError, new Exception("Stack Trace Dump"));
- }
-
if (isDone()) {
- if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
}
mCancelled = true;
@@ -428,6 +392,7 @@
// First send callback to the app directly...
try {
+ Slog.i(TAG, "Sending onFailure to app due to reason=" + cancelReason);
mAppCallback.onFailure(cancelReason);
} catch (RemoteException ignore) {
}
@@ -437,7 +402,7 @@
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
- Log.i(TAG, "setResultAndFinish(), association="
+ Slog.i(TAG, "setResultAndFinish(), association="
+ (association == null ? "null" : association)
+ "resultCode=" + resultCode);
@@ -454,7 +419,7 @@
}
private void initUiForSelfManagedAssociation() {
- if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
+ Slog.d(TAG, "initUiForSelfManagedAssociation()");
final CharSequence deviceName = mRequest.getDisplayName();
final String deviceProfile = mRequest.getDeviceProfile();
@@ -477,7 +442,7 @@
mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
}
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
+ Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
cancel(/* discoveryTimeout */ false,
/* userRejected */ false, /* internalError */ true);
return;
@@ -506,8 +471,8 @@
mBorderBottom.setVisibility(View.GONE);
}
- private void initUiForSingleDevice(CharSequence appLabel) {
- if (DEBUG) Log.i(TAG, "initUiFor_SingleDevice()");
+ private void initUiForSingleDevice() {
+ Slog.d(TAG, "initUiForSingleDevice()");
final String deviceProfile = mRequest.getDeviceProfile();
@@ -515,9 +480,16 @@
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
- CompanionDeviceDiscoveryService.getScanResult().observe(this,
- deviceFilterPairs -> updateSingleDeviceUi(
- deviceFilterPairs, deviceProfile, appLabel));
+ final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
+ mProfileIcon.setImageDrawable(profileIcon);
+
+ CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> {
+ if (deviceFilterPairs.isEmpty()) {
+ return;
+ }
+ mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+ updateSingleDeviceUi();
+ });
mSingleDeviceSpinner.setVisibility(View.VISIBLE);
// Hide permission list and confirmation dialog first before the
@@ -527,33 +499,8 @@
mAssociationConfirmationDialog.setVisibility(View.GONE);
}
- private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs,
- String deviceProfile, CharSequence appLabel) {
- // Ignore "empty" scan reports.
- if (deviceFilterPairs.isEmpty()) return;
-
- mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
-
- final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
-
- // No need to show permission consent dialog if it is a isSkipPrompt(true)
- // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
- if (mRequest.isSkipPrompt()) {
- Log.d(TAG, "Skipping the permission consent dialog.");
- mSingleDeviceSpinner.setVisibility(View.GONE);
- onUserSelectedDevice(mSelectedDevice);
- return;
- }
-
- updatePermissionUi();
-
- mProfileIcon.setImageDrawable(profileIcon);
- mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
- mSingleDeviceSpinner.setVisibility(View.GONE);
- }
-
- private void initUiForMultipleDevices(CharSequence appLabel) {
- if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
+ private void initUiForMultipleDevices() {
+ Slog.d(TAG, "initUiForMultipleDevices()");
final Drawable profileIcon;
final Spanned title;
@@ -566,7 +513,7 @@
profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
if (deviceProfile == null) {
- title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
+ title = getHtmlFromResources(this, R.string.chooser_title_non_profile, mAppLabel);
mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
} else {
title = getHtmlFromResources(this,
@@ -606,7 +553,7 @@
final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
// To prevent double tap on the selected device.
if (mSelectedDevice != null) {
- if (DEBUG) Log.w(TAG, "Already selected.");
+ Slog.w(TAG, "Already selected.");
return;
}
// Notify the adapter to highlight the selected item.
@@ -614,17 +561,9 @@
mSelectedDevice = requireNonNull(selectedDevice);
- Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
+ Slog.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
- // No need to show permission consent dialog if it is a isSkipPrompt(true)
- // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
- if (mRequest.isSkipPrompt()) {
- Log.d(TAG, "Skipping the permission consent dialog.");
- onUserSelectedDevice(mSelectedDevice);
- return;
- }
-
- updatePermissionUi();
+ updateSingleDeviceUi();
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
@@ -633,7 +572,18 @@
mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
}
- private void updatePermissionUi() {
+ private void updateSingleDeviceUi() {
+ // No need to show permission consent dialog if it is a isSkipPrompt(true)
+ // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ if (mRequest.isSkipPrompt()) {
+ Slog.d(TAG, "Skipping the permission consent dialog.");
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
+
+ mSingleDeviceSpinner.setVisibility(View.GONE);
+ mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
+
final String deviceProfile = mRequest.getDeviceProfile();
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final String remoteDeviceName = mSelectedDevice.getDisplayName();
@@ -658,7 +608,7 @@
}
private void onPositiveButtonClick(View v) {
- if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()");
+ Slog.d(TAG, "onPositiveButtonClick()");
// Disable the button, to prevent more clicks.
v.setEnabled(false);
@@ -671,7 +621,7 @@
}
private void onNegativeButtonClick(View v) {
- if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()");
+ Slog.d(TAG, "onNegativeButtonClick()");
// Disable the button, to prevent more clicks.
v.setEnabled(false);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 65bbb6fc..a5bb34f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -55,7 +55,7 @@
import android.os.Parcelable;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Log;
+import android.util.Slog;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -71,7 +71,6 @@
*/
@SuppressLint("LongLogTag")
public class CompanionDeviceDiscoveryService extends Service {
- private static final boolean DEBUG = false;
private static final String TAG = "CDM_CompanionDeviceDiscoveryService";
private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
@@ -147,7 +146,6 @@
@Override
public void onCreate() {
super.onCreate();
- if (DEBUG) Log.d(TAG, "onCreate()");
mBtManager = getSystemService(BluetoothManager.class);
mBtAdapter = mBtManager.getAdapter();
@@ -158,7 +156,6 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "onStartCommand() action=" + action);
switch (action) {
case ACTION_START_DISCOVERY:
@@ -174,15 +171,9 @@
return START_NOT_STICKY;
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Log.d(TAG, "onDestroy()");
- }
-
@MainThread
private void startDiscovery(@NonNull AssociationRequest request) {
- if (DEBUG) Log.i(TAG, "startDiscovery() request=" + request);
+ Slog.d(TAG, "startDiscovery() request=" + request);
requireNonNull(request);
if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
@@ -197,7 +188,12 @@
filter(allFilters, BluetoothLeDeviceFilter.class);
final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
- checkBoundDevicesIfNeeded(request, btFilters);
+ // No need to startDiscovery if the device is already bound or connected for
+ // singleDevice dialog.
+ if (checkBoundDevicesIfNeeded(request, btFilters)) {
+ stopSelf();
+ return;
+ }
// If no filters are specified: look for everything.
final boolean forceStartScanningAll = isEmpty(allFilters);
@@ -213,7 +209,7 @@
@MainThread
private void stopDiscoveryAndFinish(boolean timeout) {
- if (DEBUG) Log.i(TAG, "stopDiscovery()");
+ Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")");
if (!mDiscoveryStarted) {
stopSelf();
@@ -257,42 +253,45 @@
stopSelf();
}
- private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+ private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
@NonNull List<BluetoothDeviceFilter> btFilters) {
// If filtering to get single device by mac address, also search in the set of already
// bonded devices to allow linking those directly
- if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+ if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
final BluetoothDeviceFilter singleMacAddressFilter =
find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
- if (singleMacAddressFilter == null) return;
+ if (singleMacAddressFilter == null) return false;
- findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
- findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
- findAndReportMatches(
- mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+ return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT_SERVER), btFilters);
}
- private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+ private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
@NonNull List<BluetoothDeviceFilter> filters) {
- if (devices == null) return;
+ if (devices == null) return false;
for (BluetoothDevice device : devices) {
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
if (match != null) {
onDeviceFound(match);
+ return true;
}
}
+
+ return false;
}
private BluetoothBroadcastReceiver startBtScanningIfNeeded(
List<BluetoothDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
+ Slog.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
-
final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, intentFilter);
@@ -304,7 +303,7 @@
private WifiBroadcastReceiver startWifiScanningIfNeeded(
List<WifiDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
+ Slog.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
@@ -320,10 +319,10 @@
private ScanCallback startBleScanningIfNeeded(
List<BluetoothLeDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "BLEScanner.startScan");
+ Slog.d(TAG, "BLEScanner.startScan");
if (mBleScanner == null) {
- Log.w(TAG, "BLE Scanner is not available.");
+ Slog.w(TAG, "BLE Scanner is not available.");
return null;
}
@@ -341,18 +340,13 @@
private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
runOnMainThread(() -> {
- if (DEBUG) Log.v(TAG, "onDeviceFound() " + device);
if (mDiscoveryStopped) return;
if (mDevicesFound.contains(device)) {
// TODO: update the device instead of ignoring (new found device may contain
// additional/updated info, eg. name of the device).
- if (DEBUG) {
- Log.d(TAG, "onDeviceFound() " + device.toShortString()
- + " - Already seen: ignore.");
- }
return;
}
- Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+ Slog.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
// First: make change.
mDevicesFound.add(device);
@@ -367,7 +361,7 @@
private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
runOnMainThread(() -> {
- Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+ Slog.i(TAG, "onDeviceLost(), device=" + device.toShortString());
// First: make change.
mDevicesFound.remove(device);
@@ -386,13 +380,10 @@
timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
}
- if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout);
-
Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
}
private void timeout() {
- if (DEBUG) Log.i(TAG, "timeout()");
stopDiscoveryAndFinish(/* timeout */ true);
}
@@ -410,10 +401,6 @@
@Override
public void onScanResult(int callbackType, ScanResult result) {
- if (DEBUG) {
- Log.v(TAG, "BLE.onScanResult() callback=" + callbackType + ", result=" + result);
- }
-
final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
if (match == null) return;
@@ -438,8 +425,6 @@
final String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (DEBUG) Log.v(TAG, action + ", device=" + device);
-
if (action == null) return;
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
@@ -468,10 +453,6 @@
}
final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
- if (DEBUG) {
- Log.v(TAG, "WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, results:\n "
- + TextUtils.join("\n ", scanResults));
- }
for (int i = 0; i < scanResults.size(); i++) {
final android.net.wifi.ScanResult scanResult = scanResults.get(i);
@@ -496,9 +477,7 @@
DeviceFilterPair<T> result = matchingFilter != null
? new DeviceFilterPair<>(dev, matchingFilter) : null;
- if (DEBUG) {
- Log.v(TAG, "findMatch(dev=" + dev + ", filters=" + filters + ") -> " + result);
- }
+
return result;
}
}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
deleted file mode 100644
index fa4d6af..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
- public static final File[] EMPTY_FILE = new File[0];
-
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(@Nullable T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-
- /** @hide */
- public static @NonNull File[] defeatNullable(@Nullable File[] val) {
- return (val != null) ? val : EMPTY_FILE;
- }
-
- /**
- * Checks if given array is null or has zero elements.
- */
- public static boolean isEmpty(@Nullable int[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * True if the byte array is null or has length 0.
- */
- public static boolean isEmpty(@Nullable byte[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Converts from List of bytes to byte array
- * @param list
- * @return byte[]
- */
- public static byte[] toPrimitive(List<byte[]> list) {
- if (list.size() == 0) {
- return new byte[0];
- }
- int byteLen = list.get(0).length;
- byte[] array = new byte[list.size() * byteLen];
- for (int i = 0; i < list.size(); i++) {
- for (int j = 0; j < list.get(i).length; j++) {
- array[i * byteLen + j] = list.get(i)[j];
- }
- }
- return array;
- }
-
- /**
- * Adds value to given array if not already present, providing set-like
- * behavior.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
- return appendInt(cur, val, false);
- }
-
- /**
- * Adds value to given array.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
- boolean allowDuplicates) {
- if (cur == null) {
- return new int[] { val };
- }
- final int n = cur.length;
- if (!allowDuplicates) {
- for (int i = 0; i < n; i++) {
- if (cur[i] == val) {
- return cur;
- }
- }
- }
- int[] ret = new int[n + 1];
- System.arraycopy(cur, 0, ret, 0, n);
- ret[n] = val;
- return ret;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
deleted file mode 100644
index afcf689..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Thread for asynchronous event processing. This thread is configured as
- * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
- * resources will be dedicated to it, and it will "have less chance of impacting
- * the responsiveness of the user interface."
- * <p>
- * This thread is best suited for tasks that the user is not actively waiting
- * for, or for tasks that the user expects to be executed eventually.
- *
- * @see com.android.internal.os.BackgroundThread
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public final class BackgroundThread extends HandlerThread {
- private static final Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static BackgroundThread sInstance;
- @GuardedBy("sLock")
- private static Handler sHandler;
- @GuardedBy("sLock")
- private static HandlerExecutor sHandlerExecutor;
-
- private BackgroundThread() {
- super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
- }
-
- @GuardedBy("sLock")
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new BackgroundThread();
- sInstance.start();
- sHandler = new Handler(sInstance.getLooper());
- sHandlerExecutor = new HandlerExecutor(sHandler);
- }
- }
-
- /**
- * Get the singleton instance of this class.
- *
- * @return the singleton instance of this class
- */
- @NonNull
- public static BackgroundThread get() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sInstance;
- }
- }
-
- /**
- * Get the singleton {@link Handler} for this class.
- *
- * @return the singleton {@link Handler} for this class.
- */
- @NonNull
- public static Handler getHandler() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-
- /**
- * Get the singleton {@link Executor} for this class.
- *
- * @return the singleton {@link Executor} for this class.
- */
- @NonNull
- public static Executor getExecutor() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandlerExecutor;
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
deleted file mode 100644
index e4923bf..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
- /**
- * Read a text file into a String, optionally limiting the length.
- *
- * @param file to read (will not seek, so things like /proc files are OK)
- * @param max length (positive for head, negative of tail, 0 for no limit)
- * @param ellipsis to add of the file was truncated (can be null)
- * @return the contents of the file, possibly truncated
- * @throws IOException if something goes wrong reading the file
- * @hide
- */
- public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
- @Nullable String ellipsis) throws IOException {
- InputStream input = new FileInputStream(file);
- // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
- // input stream, bytes read not equal to buffer size is not necessarily the correct
- // indication for EOF; but it is true for BufferedInputStream due to its implementation.
- BufferedInputStream bis = new BufferedInputStream(input);
- try {
- long size = file.length();
- if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
- if (size > 0 && (max == 0 || size < max)) max = (int) size;
- byte[] data = new byte[max + 1];
- int length = bis.read(data);
- if (length <= 0) return "";
- if (length <= max) return new String(data, 0, length);
- if (ellipsis == null) return new String(data, 0, max);
- return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: keep the last N
- int len;
- boolean rolled = false;
- byte[] last = null;
- byte[] data = null;
- do {
- if (last != null) rolled = true;
- byte[] tmp = last;
- last = data;
- data = tmp;
- if (data == null) data = new byte[-max];
- len = bis.read(data);
- } while (len == data.length);
-
- if (last == null && len <= 0) return "";
- if (last == null) return new String(data, 0, len);
- if (len > 0) {
- rolled = true;
- System.arraycopy(last, len, last, 0, last.length - len);
- System.arraycopy(data, 0, last, last.length - len, len);
- }
- if (ellipsis == null || !rolled) return new String(last);
- return ellipsis + new String(last);
- } else { // "cat" mode: size unknown, read it all in streaming fashion
- ByteArrayOutputStream contents = new ByteArrayOutputStream();
- int len;
- byte[] data = new byte[1024];
- do {
- len = bis.read(data);
- if (len > 0) contents.write(data, 0, len);
- } while (len == data.length);
- return contents.toString();
- }
- } finally {
- bis.close();
- input.close();
- }
- }
-
- /**
- * Perform an fsync on the given FileOutputStream. The stream at this
- * point must be flushed but not yet closed.
- *
- * @hide
- */
- public static boolean sync(FileOutputStream stream) {
- try {
- if (stream != null) {
- stream.getFD().sync();
- }
- return true;
- } catch (IOException e) {
- }
- return false;
- }
-
- /**
- * List the files in the directory or return empty file.
- *
- * @hide
- */
- public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
- return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
- : ArrayUtils.EMPTY_FILE;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
deleted file mode 100644
index fdb15e2..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * An adapter {@link Executor} that posts all executed tasks onto the given
- * {@link Handler}.
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public class HandlerExecutor implements Executor {
- private final Handler mHandler;
-
- public HandlerExecutor(@NonNull Handler handler) {
- mHandler = Objects.requireNonNull(handler);
- }
-
- @Override
- public void execute(Runnable command) {
- if (!mHandler.post(command)) {
- throw new RejectedExecutionException(mHandler + " is shutting down");
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
deleted file mode 100644
index 5cdc253..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
- private long[] mValues;
- private int mSize;
- private int mHead;
- private int mTail;
-
- private long[] newUnpaddedLongArray(int num) {
- return new long[num];
- }
- /**
- * Initializes a queue with the given starting capacity.
- *
- * @param initialCapacity the capacity.
- */
- public LongArrayQueue(int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.LONG;
- } else {
- mValues = newUnpaddedLongArray(initialCapacity);
- }
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Initializes a queue with default starting capacity.
- */
- public LongArrayQueue() {
- this(16);
- }
-
- /** @hide */
- public static int growSize(int currentSize) {
- return currentSize <= 4 ? 8 : currentSize * 2;
- }
-
- private void grow() {
- if (mSize < mValues.length) {
- throw new IllegalStateException("Queue not full yet!");
- }
- final int newSize = growSize(mSize);
- final long[] newArray = newUnpaddedLongArray(newSize);
- final int r = mValues.length - mHead; // Number of elements on and to the right of head.
- System.arraycopy(mValues, mHead, newArray, 0, r);
- System.arraycopy(mValues, 0, newArray, r, mHead);
- mValues = newArray;
- mHead = 0;
- mTail = mSize;
- }
-
- /**
- * Returns the number of elements in the queue.
- */
- public int size() {
- return mSize;
- }
-
- /**
- * Removes all elements from this queue.
- */
- public void clear() {
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Adds a value to the tail of the queue.
- *
- * @param value the value to be added.
- */
- public void addLast(long value) {
- if (mSize == mValues.length) {
- grow();
- }
- mValues[mTail] = value;
- mTail = (mTail + 1) % mValues.length;
- mSize++;
- }
-
- /**
- * Removes an element from the head of the queue.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long removeFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final long ret = mValues[mHead];
- mHead = (mHead + 1) % mValues.length;
- mSize--;
- return ret;
- }
-
- /**
- * Returns the element at the given position from the head of the queue, where 0 represents the
- * head of the queue.
- *
- * @param position the position from the head of the queue.
- * @return the element found at the given position.
- * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
- * {@code position} >= {@link #size()}
- */
- public long get(int position) {
- if (position < 0 || position >= mSize) {
- throw new IndexOutOfBoundsException("Index " + position
- + " not valid for a queue of size " + mSize);
- }
- final int index = (mHead + position) % mValues.length;
- return mValues[index];
- }
-
- /**
- * Returns the element at the head of the queue, without removing it.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty
- */
- public long peekFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- return mValues[mHead];
- }
-
- /**
- * Returns the element at the tail of the queue.
- *
- * @return the element at the tail of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long peekLast() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
- return mValues[index];
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- if (mSize <= 0) {
- return "{}";
- }
-
- final StringBuilder buffer = new StringBuilder(mSize * 64);
- buffer.append('{');
- buffer.append(get(0));
- for (int i = 1; i < mSize; i++) {
- buffer.append(", ");
- buffer.append(get(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index dbbef61..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.utils;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
- private static final String STRING_ARRAY_SEPARATOR = ":";
-
- /** @hide */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- int type;
- while ((type = parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- // Do nothing
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /** @hide */
- public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
- throws IOException, XmlPullParserException {
- for (;;) {
- int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT
- || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
- return false;
- }
- if (type == XmlPullParser.START_TAG
- && parser.getDepth() == outerDepth + 1) {
- return true;
- }
- }
- }
-
- private static XmlPullParser newPullParser() {
- try {
- XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
- } catch (XmlPullParserException e) {
- throw new AssertionError();
- }
- }
-
- /** @hide */
- public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
- throws IOException {
- final byte[] magic = new byte[4];
- if (in instanceof FileInputStream) {
- try {
- Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
- } else {
- if (!in.markSupported()) {
- in = new BufferedInputStream(in);
- }
- in.mark(8);
- in.read(magic);
- in.reset();
- }
-
- final TypedXmlPullParser xml;
- xml = (TypedXmlPullParser) newPullParser();
- try {
- xml.setInput(in, "UTF_8");
- } catch (XmlPullParserException e) {
- throw new IOException(e);
- }
- return xml;
- }
-}
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index e998fe8..2aff2c3 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentStart="true"
- android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
app:tint="?androidprv:attr/materialColorOnSurface"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9db681a..46a5138 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -121,16 +121,10 @@
<string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the get flow. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_sign_in_for">Use your account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+ <string name="get_dialog_description_single_tap">Use your screen lock to sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
<string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 429bdbf..7bc3241 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -317,6 +317,14 @@
)
}
+ fun createFlowOnMoreOptionsOnlySelectedOnCreationSelection() {
+ uiState = uiState.copy(
+ createCredentialUiState = uiState.createCredentialUiState?.copy(
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION_ONLY,
+ )
+ )
+ }
+
fun createFlowOnBackCreationSelectionButtonSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..4109079 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -47,9 +47,9 @@
import android.service.credentials.CredentialProviderService
import android.util.Log
import android.content.Intent
-import android.os.IBinder
import android.view.autofill.AutofillId
import android.view.autofill.AutofillManager
+import android.view.autofill.IAutoFillManagerClient
import android.widget.RemoteViews
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
@@ -95,7 +95,7 @@
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback,
- autofillCallback: IBinder
+ autofillCallback: IAutoFillManagerClient
) {
val context = request.fillContexts
val structure = context[context.size - 1].structure
@@ -160,7 +160,7 @@
CancellationSignal(),
Executors.newSingleThreadExecutor(),
outcome,
- autofillCallback
+ autofillCallback.asBinder()
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index 6bf803a..0d19a45 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -34,7 +34,6 @@
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
-import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -210,7 +209,7 @@
onCancelFlowAndFinish: () -> Unit
) {
try {
- if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+ if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
onBiometricFailureFallback(biometricFlowType)
return
}
@@ -250,40 +249,40 @@
* consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
* occur if *only* device credentials are available, to avoid going right into the PIN screen.
* Note that if device credential is the only available modality but not requested, or if none
- * of the requested modalities are available, we propagate the error to the provider instead of
- * falling back and expect them to handle it as they would prior.
- * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases
+ * of the requested modalities are available, we fallback to the normal flow to ensure a selector
+ * shows up.
+ * // TODO(b/334197980) : While we already fallback in cases the selector doesn't show, confirm
+ * // final plan.
*/
-private fun onlyUsingDeviceCredentials(
+private fun canCallBiometricPrompt(
biometricDisplayInfo: BiometricDisplayInfo,
context: Context
): Boolean {
val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
- return true
- }
-
- val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
- allowedAuthenticators)
-
- if (!allowedAuthContainsDeviceCredential) {
- // At this point, allowed authenticators is requesting biometrics without device creds.
- // Thus, a fallback mechanism will be displayed via our own negative button - "cancel".
- // Beyond this point, fallbacks will occur if none of the stronger authenticators can
- // be used.
return false
}
val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
- if (allowedAuthContainsDeviceCredential &&
- biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
- BiometricManager.BIOMETRIC_SUCCESS &&
- biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+ if (biometricManager.canAuthenticate(allowedAuthenticators) !=
BiometricManager.BIOMETRIC_SUCCESS) {
- return true
+ return false
}
+ if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false
+
+ return true
+}
+
+private fun ifOnlySupportsAtMostDeviceCredentials(biometricManager: BiometricManager): Boolean {
+ if (biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+ BiometricManager.BIOMETRIC_SUCCESS &&
+ biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+ BiometricManager.BIOMETRIC_SUCCESS
+ ) {
+ return true
+ }
return false
}
@@ -480,16 +479,7 @@
)
descriptionText = context.getString(
- when (singleEntryType) {
- CredentialType.PASSKEY ->
- R.string.get_dialog_description_single_tap_passkey
-
- CredentialType.PASSWORD ->
- R.string.get_dialog_description_single_tap_password
-
- CredentialType.UNKNOWN ->
- R.string.get_dialog_description_single_tap_saved_sign_in
- },
+ R.string.get_dialog_description_single_tap,
getRequestDisplayInfo.appName,
username
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 149c14a..2c3c63b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -28,7 +28,6 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -47,7 +46,6 @@
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -55,7 +53,6 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.Shapes
@@ -321,6 +318,8 @@
fun MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
+ navigationIcon: ImageVector,
+ navigationIconContentDescription: String,
bottomPadding: Dp,
) {
Row(
@@ -336,40 +335,6 @@
contentAlignment = Alignment.Center,
) {
Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = stringResource(
- R.string.accessibility_back_arrow_button
- ),
- modifier = Modifier.size(24.dp).autoMirrored(),
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- )
- }
- }
- LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
- }
-}
-
-@Composable
-fun MoreOptionTopAppBarWithCustomNavigation(
- text: String,
- onNavigationIconClicked: () -> Unit,
- navigationIcon: ImageVector,
- navigationIconContentDescription: String,
- bottomPadding: Dp,
-) {
- Row(
- modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- IconButton(
- modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
- onClick = onNavigationIconClicked
- ) {
- Box(
- modifier = Modifier.size(48.dp),
- contentAlignment = Alignment.Center,
- ) {
- Icon(
imageVector = navigationIcon,
contentDescription = navigationIconContentDescription,
modifier = Modifier.size(24.dp).autoMirrored(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index a0915d2..282a1b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -32,6 +32,8 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -107,7 +109,7 @@
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
onMoreOptionSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection,
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderInfo = createCredentialUiState
.activeEntry?.activeProvider!!,
@@ -120,6 +122,41 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange
)
+ CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState.disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnLaunchSettings,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
+ customTopAppBar = { MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (createCredentialUiState.requestDisplayInfo
+ .type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(
+ R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = viewModel::onUserCancel,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.Close,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_close_button
+ )
+ )}
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -207,22 +244,31 @@
onDisabledProvidersSelected: () -> Unit,
onRemoteEntrySelected: (EntryInfo) -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
+ customTopAppBar: (@Composable() () -> Unit)? = null
) {
SheetContainerCard(topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(
- R.string.save_credential_to_title,
- when (requestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- stringResource(R.string.passkey)
- CredentialType.PASSWORD ->
- stringResource(R.string.password)
- CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
- }
- ),
- onNavigationIconClicked = onBackCreationSelectionButtonSelected,
- bottomPadding = 16.dp,
- )
+ if (customTopAppBar != null) {
+ customTopAppBar()
+ } else {
+ MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (requestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = onBackCreationSelectionButtonSelected,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ )
+ )
+ }
}) {
// bottom padding already
item {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ddd4139..130937c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -181,4 +181,5 @@
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
+ MORE_OPTIONS_SELECTION_ONLY,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ce4f402..c98bb5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,7 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.material3.Divider
@@ -71,7 +72,6 @@
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
-import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation
import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.common.ui.SnackbarActionText
@@ -175,7 +175,7 @@
onBackButtonClicked = viewModel::onUserCancel,
onCancel = viewModel::onUserCancel,
onLog = { viewModel.logUiEvent(it) },
- customTopBar = { MoreOptionTopAppBarWithCustomNavigation(
+ customTopBar = { MoreOptionTopAppBar(
text = stringResource(
R.string.get_dialog_title_sign_in_options),
onNavigationIconClicked = viewModel::onUserCancel,
@@ -683,7 +683,10 @@
text = stringResource(R.string.get_dialog_title_sign_in_options),
onNavigationIconClicked = onBackButtonClicked,
bottomPadding = 0.dp,
- )
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ ))
}
}) {
var isFirstSection = true
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -78,6 +79,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d69..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
+ <!-- Wearable Components -->
+ <service android:name=".wear.WearPackageInstallerService"
+ android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+ android:foregroundServiceType="systemExempted"
+ android:exported="true"/>
+
+ <provider android:name=".wear.WearPackageIconProvider"
+ android:authorities="com.google.android.packageinstaller.wear.provider"
+ android:grantUriPermissions="true"
+ android:exported="false" />
+
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 0000000..53a460d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+ private static final String TAG = "InstallTask";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ private final Context mContext;
+ private String mPackageName;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private PackageInstallerImpl.InstallListener mCallback;
+ private PackageInstaller.Session mSession;
+ private IntentSender mCommitCallback;
+
+ private Exception mException = null;
+ private int mErrorCode = 0;
+ private String mErrorDesc = null;
+
+ public InstallTask(Context context, String packageName,
+ ParcelFileDescriptor parcelFileDescriptor,
+ PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+ IntentSender commitCallback) {
+ mContext = context;
+ mPackageName = packageName;
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mCallback = callback;
+ mSession = session;
+ mCommitCallback = commitCallback;
+ }
+
+ public boolean isError() {
+ return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+ }
+
+ public void execute() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("This method cannot be called from the UI thread.");
+ }
+
+ OutputStream sessionStream = null;
+ try {
+ sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+ // 2b: Stream the asset to the installer. Note:
+ // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+ writeToOutputStreamFromAsset(sessionStream);
+ mSession.fsync(sessionStream);
+ } catch (Exception e) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+ mErrorDesc = "Could not write to stream";
+ } finally {
+ if (sessionStream != null) {
+ // 2c: close output stream
+ try {
+ sessionStream.close();
+ } catch (Exception e) {
+ // Ignore otherwise
+ if (mException == null) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+ mErrorDesc = "Could not close session stream";
+ }
+ }
+ }
+ }
+
+ if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+ // An error occurred, we're done
+ Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+ + mErrorDesc + ", " + mException);
+ mSession.close();
+ mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+ } else {
+ // 3. Commit the session (this actually installs it.) Session map
+ // will be cleaned up in the callback.
+ mCallback.installBeginning();
+ mSession.commit(mCommitCallback);
+ mSession.close();
+ }
+ }
+
+ /**
+ * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+ * corresponding to the {@code Asset} and then write the contents into an
+ * {@code OutputStream} that is passed in.
+ * <br>
+ * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+ */
+ private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+ if (outputStream == null) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+ mErrorDesc = "Got a null OutputStream.";
+ return false;
+ }
+
+ if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
+ mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+ mErrorDesc = "Could not get FD";
+ return false;
+ }
+
+ InputStream inputStream = null;
+ try {
+ byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead;
+ inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+ while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+ if (bytesRead > 0) {
+ outputStream.write(inputBuf, 0, bytesRead);
+ }
+ }
+
+ outputStream.flush();
+ } catch (IOException e) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+ mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+ return false;
+ } finally {
+ safeClose(inputStream);
+ }
+
+ return true;
+ }
+
+ /**
+ * Quietly close a closeable resource (e.g. a stream or file). The input may already
+ * be closed and it may even be null.
+ */
+ public static void safeClose(Closeable resource) {
+ if (resource != null) {
+ try {
+ resource.close();
+ } catch (IOException ioe) {
+ // Catch and discard the error
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 0000000..3daf3d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+ /** Request succeeded */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The new PackageInstaller also returns a small set of less granular error codes, which
+ * we'll remap to the range -500 and below to keep away from existing installer codes
+ * (which run from -1 to -110).
+ */
+ public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+ public static final int ERROR_COULD_NOT_GET_FD = -603;
+ /** This node is not targeted by this request. */
+
+ /** The install did not complete because could not create PackageInstaller session */
+ public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+ /** The install did not complete because could not open PackageInstaller session */
+ public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+ /** The install did not complete because could not open PackageInstaller output stream */
+ public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+ /** The install did not complete because of an exception while streaming bytes */
+ public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+ /** The install did not complete because of an unexpected exception from PackageInstaller */
+ public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+ /** The install did not complete because of an unexpected userActionRequired callback */
+ public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+ /** The install did not complete because of an unexpected broadcast (missing fields) */
+ public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+ /** The install did not complete because of an error while copying from downloaded file */
+ public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+ /** The install did not complete because of an error while copying to the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_COPY_STREAM = -620;
+ /** The install did not complete because of an error while closing the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 0000000..bdc22cf
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+ private static PackageInstallerImpl sPackageInstaller;
+
+ /**
+ * Return the PackageInstaller shared object. {@code init} should have already been called.
+ */
+ public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+ if (sPackageInstaller == null) {
+ sPackageInstaller = new PackageInstallerImpl(context);
+ }
+ return sPackageInstaller;
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 0000000..1e37f15
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+ private static final String TAG = "PackageInstallerImpl";
+
+ /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+ private final Context mContext;
+ private final PackageInstaller mPackageInstaller;
+ private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+ private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+ public PackageInstallerImpl(Context context) {
+ mContext = context.getApplicationContext();
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+ // Capture a map of known sessions
+ // This list will be pruned a bit later (stale sessions will be canceled)
+ mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+ List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+ for (int i = 0; i < mySessions.size(); i++) {
+ PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+ String packageName = sessionInfo.getAppPackageName();
+ PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+ // Checking for old info is strictly for logging purposes
+ if (oldInfo != null) {
+ Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+ .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+ }
+ }
+ mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+ }
+
+ /**
+ * This callback will be made after an installation attempt succeeds or fails.
+ */
+ public interface InstallListener {
+ /**
+ * This callback signals that preflight checks have succeeded and installation
+ * is beginning.
+ */
+ void installBeginning();
+
+ /**
+ * This callback signals that installation has completed.
+ */
+ void installSucceeded();
+
+ /**
+ * This callback signals that installation has failed.
+ */
+ void installFailed(int errorCode, String errorDesc);
+ }
+
+ /**
+ * This is a placeholder implementation that bundles an entire "session" into a single
+ * call. This will be replaced by more granular versions that allow longer session lifetimes,
+ * download progress tracking, etc.
+ *
+ * This must not be called on main thread.
+ */
+ public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+ final InstallListener callback) {
+ // 0. Generic try/catch block because I am not really sure what exceptions (other than
+ // IOException) might be thrown by PackageInstaller and I want to handle them
+ // at least slightly gracefully.
+ try {
+ // 1. Create or recover a session, and open it
+ // Try recovery first
+ PackageInstaller.Session session = null;
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo != null) {
+ // See if it's openable, or already held open
+ session = getSession(packageName);
+ }
+ // If open failed, or there was no session, create a new one and open it.
+ // If we cannot create or open here, the failure is terminal.
+ if (session == null) {
+ try {
+ innerCreateSession(packageName);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+ "Could not create session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ sessionInfo = mSessionInfoMap.get(packageName);
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ mOpenSessionMap.put(packageName, session);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+ "Can't open session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ }
+
+ // 2. Launch task to handle file operations.
+ InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+ callback, session,
+ getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+ task.execute();
+ if (task.isError()) {
+ cancelSession(sessionInfo.getSessionId(), packageName);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+ + e.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+ "Unexpected exception while installing " + packageName);
+ }
+ }
+
+ /**
+ * Retrieve an existing session. Will open if needed, but does not attempt to create.
+ */
+ private PackageInstaller.Session getSession(String packageName) {
+ // Check for already-open session
+ PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+ if (session != null) {
+ try {
+ // Probe the session to ensure that it's still open. This may or may not
+ // throw (if non-open), but it may serve as a canary for stale sessions.
+ session.getNames();
+ return session;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+ mOpenSessionMap.remove(packageName);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+ mOpenSessionMap.remove(packageName);
+ }
+ }
+ // Check to see if this is a known session
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo == null) {
+ return null;
+ }
+ // Try to open it. If we fail here, assume that the SessionInfo was stale.
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ } catch (SecurityException se) {
+ Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ } catch (IOException ioe) {
+ Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+ + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ }
+ mOpenSessionMap.put(packageName, session);
+ return session;
+ }
+
+ /** This version throws an IOException when the session cannot be created */
+ private void innerCreateSession(String packageName) throws IOException {
+ if (mSessionInfoMap.containsKey(packageName)) {
+ Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+ return;
+ }
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(packageName);
+
+ // IOException may be thrown at this point
+ int sessionId = mPackageInstaller.createSession(params);
+ PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+ mSessionInfoMap.put(packageName, sessionInfo);
+ }
+
+ /**
+ * Cancel a session based on its sessionId. Package name is for logging only.
+ */
+ private void cancelSession(int sessionId, String packageName) {
+ // Close if currently held open
+ closeSession(packageName);
+ // Remove local record
+ mSessionInfoMap.remove(packageName);
+ try {
+ mPackageInstaller.abandonSession(sessionId);
+ } catch (SecurityException se) {
+ // The session no longer exists, so we can exit quietly.
+ return;
+ }
+ }
+
+ /**
+ * Close a session if it happens to be held open.
+ */
+ private void closeSession(String packageName) {
+ PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+ if (session != null) {
+ // Unfortunately close() is not idempotent. Try our best to make this safe.
+ try {
+ session.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+ + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Creates a commit callback for the package install that's underway. This will be called
+ * some time after calling session.commit() (above).
+ */
+ private IntentSender getCommitCallback(final String packageName, final int sessionId,
+ final InstallListener callback) {
+ // Create a single-use broadcast receiver
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext.unregisterReceiver(this);
+ handleCommitCallback(intent, packageName, sessionId, callback);
+ }
+ };
+ // Create a matching intent-filter and register the receiver
+ String action = ACTION_INSTALL_COMMIT + "." + packageName;
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(action);
+ mContext.registerReceiver(broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+
+ // Create a matching PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+ broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+ return pendingIntent.getIntentSender();
+ }
+
+ /**
+ * Examine the extras to determine information about the package update/install, decode
+ * the result, and call the appropriate callback.
+ *
+ * @param intent The intent, which the PackageInstaller will have added Extras to
+ * @param packageName The package name we created the receiver for
+ * @param sessionId The session Id we created the receiver for
+ * @param callback The callback to report success/failure to
+ */
+ private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+ InstallListener callback) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installation of " + packageName + " finished with extras "
+ + intent.getExtras());
+ }
+ String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ cancelSession(sessionId, packageName);
+ callback.installSucceeded();
+ } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+ // TODO - use the constant when the correct/final name is in the SDK
+ // TODO This is unexpected, so we are treating as failure for now
+ cancelSession(sessionId, packageName);
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+ "Unexpected: user action required");
+ } else {
+ cancelSession(sessionId, packageName);
+ int errorCode = getPackageManagerErrorCode(status);
+ Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+ + statusMessage);
+ callback.installFailed(errorCode, null);
+ }
+ }
+
+ private int getPackageManagerErrorCode(int status) {
+ // This is a hack: because PackageInstaller now reports error codes
+ // with small positive values, we need to remap them into a space
+ // that is more compatible with the existing package manager error codes.
+ // See https://sites.google.com/a/google.com/universal-store/documentation
+ // /android-client/download-error-codes
+ int errorCode;
+ if (status == Integer.MIN_VALUE) {
+ errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+ } else {
+ errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+ }
+ return errorCode;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 0000000..2c289b2
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+ private static final String KEY_ASSET_URI =
+ "com.google.android.clockwork.EXTRA_ASSET_URI";
+ private static final String KEY_START_ID =
+ "com.google.android.clockwork.EXTRA_START_ID";
+ private static final String KEY_PERM_URI =
+ "com.google.android.clockwork.EXTRA_PERM_URI";
+ private static final String KEY_CHECK_PERMS =
+ "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+ private static final String KEY_SKIP_IF_SAME_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+ private static final String KEY_COMPRESSION_ALG =
+ "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+ private static final String KEY_COMPANION_SDK_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+ private static final String KEY_COMPANION_DEVICE_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+ private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+ "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+ private static final String KEY_SKIP_IF_LOWER_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+ public static String getPackageName(Bundle b) {
+ return b.getString(KEY_PACKAGE_NAME);
+ }
+
+ public static Bundle setPackageName(Bundle b, String packageName) {
+ b.putString(KEY_PACKAGE_NAME, packageName);
+ return b;
+ }
+
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
+ }
+
+ public static Uri getPermUri(Bundle b) {
+ return b.getParcelable(KEY_PERM_URI);
+ }
+
+ public static boolean checkPerms(Bundle b) {
+ return b.getBoolean(KEY_CHECK_PERMS);
+ }
+
+ public static boolean skipIfSameVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+ }
+
+ public static int getCompanionSdkVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_SDK_VERSION);
+ }
+
+ public static int getCompanionDeviceVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+ }
+
+ public static String getCompressionAlg(Bundle b) {
+ return b.getString(KEY_COMPRESSION_ALG);
+ }
+
+ public static int getStartId(Bundle b) {
+ return b.getInt(KEY_START_ID);
+ }
+
+ public static boolean skipIfLowerVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+ }
+
+ public static Bundle setStartId(Bundle b, int startId) {
+ b.putInt(KEY_START_ID, startId);
+ return b;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 0000000..02b9d29
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+ private static final String TAG = "WearPackageIconProvider";
+ public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+ private static final String REQUIRED_PERMISSION =
+ "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+ /** MIME types. */
+ public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query is not supported.");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+ return ICON_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert is not supported.");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update is not supported.");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ }
+ return null;
+ }
+
+ public static Uri getUriForPackage(final String packageName) {
+ return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+ }
+
+ private String getPackageNameFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ List<String> pathSegments = uri.getPathSegments();
+ String packageName = pathSegments.get(pathSegments.size() - 1);
+
+ if (packageName.endsWith(".icon")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf("."));
+ }
+ return packageName;
+ }
+
+ /**
+ * Make sure the calling app is either a system app or the same app or has the right permission.
+ * @throws SecurityException if the caller has insufficient permissions.
+ */
+ @TargetApi(Build.VERSION_CODES.BASE_1_1)
+ private void enforcePermissions(Uri uri) {
+ // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+ // allow System process to access this provider.
+ Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int myUid = android.os.Process.myUid();
+
+ if (uid == myUid || isSystemApp(context, pid)) {
+ return;
+ }
+
+ if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ throw new SecurityException("Permission Denial: reading "
+ + getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid);
+ }
+
+ /**
+ * From the pid of the calling process, figure out whether this is a system app or not. We do
+ * this by checking the application information corresponding to the pid and then checking if
+ * FLAG_SYSTEM is set.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ private boolean isSystemApp(Context context, int pid) {
+ // Get the Activity Manager Object
+ ActivityManager aManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ // Get the list of running Applications
+ List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+ aManager.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+ if (rapInfo.pid == pid) {
+ try {
+ PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+ rapInfo.pkgList[0], 0);
+ if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+ (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.d(TAG, pid + " is a system app.");
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find package information.", e);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 0000000..ae0f4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ * Install Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Uninstall Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Retry GMS:
+ * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+ implements EventResultPersister.EventResultObserver {
+ private static final String TAG = "WearPkgInstallerService";
+
+ private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+ private final int START_INSTALL = 1;
+ private final int START_UNINSTALL = 2;
+
+ private int mInstallNotificationId = 1;
+ private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+ private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+ private class UninstallParams {
+ public String mPackageName;
+ public PowerManager.WakeLock mLock;
+
+ UninstallParams(String packageName, PowerManager.WakeLock lock) {
+ mPackageName = packageName;
+ mLock = lock;
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_INSTALL:
+ installPackage(msg.getData());
+ break;
+ case START_UNINSTALL:
+ uninstallPackage(msg.getData());
+ break;
+ }
+ }
+ }
+ private ServiceHandler mServiceHandler;
+ private NotificationChannel mNotificationChannel;
+ private static volatile PowerManager.WakeLock lockStatic = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("PackageInstallerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!DeviceUtils.isWear(this)) {
+ Log.w(TAG, "Not running on wearable.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (intent == null) {
+ Log.w(TAG, "Got null intent.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Got install/uninstall request " + intent);
+ }
+
+ Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ Log.e(TAG, "No package URI in intent");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+ if (packageName == null) {
+ Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ if (!lock.isHeld()) {
+ lock.acquire();
+ }
+
+ Bundle intentBundle = intent.getExtras();
+ if (intentBundle == null) {
+ intentBundle = new Bundle();
+ }
+ WearPackageArgs.setStartId(intentBundle, startId);
+ WearPackageArgs.setPackageName(intentBundle, packageName);
+ Message msg;
+ String notifTitle;
+ if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_INSTALL);
+ notifTitle = getString(R.string.installing);
+ } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+ notifTitle = getString(R.string.uninstalling);
+ } else {
+ Log.e(TAG, "Unknown action : " + intent.getAction());
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+ startForeground(notifPair.first, notifPair.second);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
+ return START_NOT_STICKY;
+ }
+
+ private void installPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+ final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+ final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+ boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+ boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+ int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+ int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+ String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+ boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+ ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+ checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+ ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+ companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+ ", skipIfLowerVersion: " + skipIfLowerVersion);
+ }
+ final PackageManager pm = getPackageManager();
+ File tempFile = null;
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ boolean messageSent = false;
+ try {
+ PackageInfo existingPkgInfo = null;
+ try {
+ existingPkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+ if (existingPkgInfo != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore this exception. We could not find the package, will treat as a new
+ // installation.
+ }
+ // TODO(28021618): This was left as a temp file due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this permission logic might want to be
+ // reworked to use a stream instead of a file so that we don't need to write a
+ // file at all. Note that there might be some trickiness with opening a stream
+ // for multiple users.
+ ParcelFileDescriptor parcelFd = getContentResolver()
+ .openFileDescriptor(assetUri, "r");
+ tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+ parcelFd, packageName, compressionAlg);
+ if (tempFile == null) {
+ Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+ return;
+ }
+ PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+ if (pkgInfo == null) {
+ Log.e(TAG, "Could not parse apk information for " + packageName);
+ return;
+ }
+
+ if (!pkgInfo.packageName.equals(packageName)) {
+ Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+ packageName);
+ return;
+ }
+
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ appInfo.sourceDir = tempFile.getPath();
+ appInfo.publicSourceDir = tempFile.getPath();
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+ List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+ // Log if the installed pkg has a higher version number.
+ if (existingPkgInfo != null) {
+ long longVersionCode = pkgInfo.getLongVersionCode();
+ if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+ if (skipIfSameVersion) {
+ Log.w(TAG, "Version number (" + longVersionCode +
+ ") of new app is equal to existing app for " + packageName +
+ "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is equal to existing app for " + packageName);
+ }
+ } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+ if (skipIfLowerVersion) {
+ // Starting in Feldspar, we are not going to allow downgrades of any app.
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() +
+ ") for " + packageName + "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+ }
+ }
+
+ // Following the Android Phone model, we should only check for permissions for any
+ // newly defined perms.
+ if (existingPkgInfo.requestedPermissions != null) {
+ for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+ // If the permission is granted, then we will not ask to request it again.
+ if ((existingPkgInfo.requestedPermissionsFlags[i] &
+ PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+ " is already granted for " + packageName);
+ }
+ wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+ }
+ }
+ }
+ }
+
+ // Check that the wearable has all the features.
+ boolean hasAllFeatures = true;
+ for (FeatureInfo feature : pkgInfo.reqFeatures) {
+ if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+ (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+ Log.e(TAG, "Wearable does not have required feature: " + feature +
+ " for " + packageName);
+ hasAllFeatures = false;
+ }
+ }
+
+ if (!hasAllFeatures) {
+ return;
+ }
+
+ // Check permissions on both the new wearable package and also on the already installed
+ // wearable package.
+ // If the app is targeting API level 23, we will also start a service in ClockworkHome
+ // which will ultimately prompt the user to accept/reject permissions.
+ if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+ companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+ Log.w(TAG, "Wearable does not have enough permissions.");
+ return;
+ }
+
+ // Finally install the package.
+ ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+ PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+ new PackageInstallListener(this, lock, startId, packageName));
+
+ messageSent = true;
+ Log.i(TAG, "Sent installation request for " + packageName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+ } finally {
+ if (!messageSent) {
+ // Some error happened. If the message has been sent, we can wait for the observer
+ // which will finish the service.
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ finishService(lock, startId);
+ }
+ }
+ }
+
+ // TODO: This was left using the old PackageManager API due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this logic should be reworked to use the new
+ // PackageInstaller APIs similar to how installPackage was reworked
+ private void uninstallPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+ UninstallParams params = new UninstallParams(packageName, lock);
+ mServiceIdToParams.put(startId, params);
+
+ final PackageManager pm = getPackageManager();
+ try {
+ PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
+ // Found package, send uninstall request.
+ pm.getPackageInstaller().uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ PackageManager.DELETE_ALL_USERS,
+ pendingIntent.getIntentSender());
+
+ Log.i(TAG, "Sent delete request for " + packageName);
+ } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+ // Couldn't find the package, no need to call uninstall.
+ Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+ finishService(lock, startId);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ finishService(lock, startId);
+ }
+ }
+
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ if (mServiceIdToParams.containsKey(serviceId)) {
+ UninstallParams params = mServiceIdToParams.get(serviceId);
+ try {
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + params.mPackageName
+ + ", returnCode " + legacyStatus);
+ }
+ } finally {
+ finishService(params.mLock, serviceId);
+ }
+ }
+ }
+
+ private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+ int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+ File apkFile) {
+ // Assumption: We are running on Android O.
+ // If the Phone App is targeting M, all permissions may not have been granted to the phone
+ // app. If the Wear App is then not targeting M, there may be permissions that are not
+ // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+ // app.
+ if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ // Install the app if Wear App is ready for the new perms model.
+ return true;
+ }
+
+ if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+ // All permissions requested by the watch are already granted on the phone, no need
+ // to do anything.
+ return true;
+ }
+
+ // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+ if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+ Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+ + "phone app is targeting at least 23, will continue.");
+ }
+
+ return false;
+ }
+
+ /**
+ * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+ * perms that are granted.
+ *
+ * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+ * @return true if there is any error cases.
+ */
+ private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+ List<String> wearablePermissions) {
+ if (permUri == null) {
+ Log.e(TAG, "Permission URI is null");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+ Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+ if (permCursor == null) {
+ Log.e(TAG, "Could not get the cursor for the permissions");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+
+ Set<String> grantedPerms = new HashSet<>();
+ Set<String> ungrantedPerms = new HashSet<>();
+ while(permCursor.moveToNext()) {
+ // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+ // verify their types.
+ if (permCursor.getColumnCount() == 2
+ && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+ && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+ String perm = permCursor.getString(0);
+ Integer granted = permCursor.getInt(1);
+ if (granted == 1) {
+ grantedPerms.add(perm);
+ } else {
+ ungrantedPerms.add(perm);
+ }
+ }
+ }
+ permCursor.close();
+
+ boolean hasUngrantedPerm = false;
+ for (String wearablePerm : wearablePermissions) {
+ if (!grantedPerms.contains(wearablePerm)) {
+ hasUngrantedPerm = true;
+ if (!ungrantedPerms.contains(wearablePerm)) {
+ // This is an error condition. This means that the wearable has permissions that
+ // are not even declared in its host app. This is a developer error.
+ Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+ + "\" that is not defined in the host application's manifest.");
+ } else {
+ Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+ "\" that is not granted in the host application.");
+ }
+ }
+ }
+ return hasUngrantedPerm;
+ }
+
+ /** Finishes the service after fulfilling obligation to call startForeground. */
+ private void finishServiceEarly(int startId) {
+ Pair<Integer, Notification> notifPair = buildNotification(
+ getApplicationContext().getPackageName(), "");
+ startForeground(notifPair.first, notifPair.second);
+ finishService(null, startId);
+ }
+
+ private void finishService(PowerManager.WakeLock lock, int startId) {
+ if (lock != null && lock.isHeld()) {
+ lock.release();
+ }
+ stopSelf(startId);
+ }
+
+ private synchronized PowerManager.WakeLock getLock(Context context) {
+ if (lockStatic == null) {
+ PowerManager mgr =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ lockStatic = mgr.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+ lockStatic.setReferenceCounted(true);
+ }
+ return lockStatic;
+ }
+
+ private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+ private Context mContext;
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+ private String mApplicationPackageName;
+ private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+ int startId, String applicationPackageName) {
+ mContext = context;
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ mApplicationPackageName = applicationPackageName;
+ }
+
+ @Override
+ public void installBeginning() {
+ Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+ }
+
+ @Override
+ public void installSucceeded() {
+ try {
+ Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+ // Delete tempFile from the file system.
+ File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ @Override
+ public void installFailed(int errorCode, String errorDesc) {
+ Log.e(TAG, "Package install failed " + mApplicationPackageName
+ + ", errorCode " + errorCode);
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+ final String title) {
+ int notifId;
+ if (mNotifIdMap.containsKey(packageName)) {
+ notifId = mNotifIdMap.get(packageName);
+ } else {
+ notifId = mInstallNotificationId++;
+ mNotifIdMap.put(packageName, notifId);
+ }
+
+ if (mNotificationChannel == null) {
+ mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+ getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(mNotificationChannel);
+ }
+ return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+ .setSmallIcon(R.drawable.ic_file_download)
+ .setContentTitle(title)
+ .build());
+ }
+
+ private void getLabelAndUpdateNotification(String packageName, String title) {
+ // Update notification since we have a label now.
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+ notificationManager.notify(notifPair.first, notifPair.second);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 0000000..6a9145d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.packageinstaller.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+ private static final String TAG = "WearablePkgInstaller";
+
+ private static final String COMPRESSION_LZMA = "lzma";
+ private static final String COMPRESSION_XZ = "xz";
+
+ public static File getTemporaryFile(Context context, String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "tmp");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ File newFile = new File(newFileDir, packageName + ".apk");
+ return newFile;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ public static File getIconFile(final Context context, final String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "images/icons");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ return new File(newFileDir, packageName + ".icon");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ /**
+ * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+ * by the PackageManager, we will parse it before sending it to the PackageManager.
+ * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+ * the fd to a File.
+ *
+ * @param context
+ * @param fd FileDescriptor to convert to File
+ * @param packageName Name of package, will define the name of the file
+ * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+ * decompress it here
+ */
+ public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+ String packageName, String compressionAlg) {
+ File newFile = getTemporaryFile(context, packageName);
+ if (fd == null || fd.getFileDescriptor() == null) {
+ return null;
+ }
+ InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ try {
+ if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+ fr = new XZInputStream(fr);
+ } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+ fr = new LZMAInputStream(fr);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+ return null;
+ }
+
+ int nRead;
+ byte[] data = new byte[1024];
+ try {
+ final FileOutputStream fo = new FileOutputStream(newFile);
+ while ((nRead = fr.read(data, 0, data.length)) != -1) {
+ fo.write(data, 0, nRead);
+ }
+ fo.flush();
+ fo.close();
+ Os.chmod(newFile.getAbsolutePath(), 0644);
+ return newFile;
+ } catch (IOException e) {
+ Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+ return null;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Could not set permissions on file ", e);
+ return null;
+ } finally {
+ try {
+ fr.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the file from FD ", e);
+ }
+ }
+ }
+
+ /**
+ * @return com.google.com from expected formats like
+ * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+ */
+ public static String getSanitizedPackageName(Uri packageUri) {
+ String packageName = packageUri.getEncodedSchemeSpecificPart();
+ if (packageName != null) {
+ return packageName.replaceAll("^/+", "");
+ }
+ return packageName;
+ }
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
new file mode 100644
index 0000000..fadcf7b
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
new file mode 100644
index 0000000..0c20287
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textSize">20dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+
+ <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+ <item name="android:textSize">36dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
new file mode 100644
index 0000000..7c9d1a4
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
deleted file mode 100644
index c7fbb5f..0000000
--- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
deleted file mode 100644
index a2b9648..0000000
--- a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:orientation="vertical"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="56dp"
- android:gravity="start|top"
- android:orientation="horizontal"
- android:paddingEnd="12dp"
- android:paddingTop="16dp"
- android:paddingBottom="4dp">
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="16dp"
- android:paddingBottom="8dp"
- android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:ellipsize="marquee" />
-
- <com.android.settingslib.widget.LinkTextView
- android:id="@+id/settingslib_learn_more"
- android:text="@string/settingslib_learn_more_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingBottom="8dp"
- android:clickable="true"
- android:visibility="gone" />
- </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 0000000..e2ed1e4
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "SettingsLibGraph-srcs",
+ srcs: ["src/**/*"],
+}
+
+android_library {
+ name: "SettingsLibGraph",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+ srcs: [":SettingsLibGraph-srcs"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 0000000..93acb35
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.graph">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 0000000..9231f40
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+ private val context = preferenceManager.context
+ // the map will preserve order
+ private val updaters = mutableMapOf<String, PreferenceUpdater>()
+ private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+ /** Creates an empty [PreferenceScreen]. */
+ fun createPreferenceScreen(): PreferenceScreen =
+ preferenceManager.createPreferenceScreen(context)
+
+ /** Creates [PreferenceScreen] from resource. */
+ fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+ preferenceManager.inflateFromResource(context, xmlRes, null)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+ addPreferenceUpdater(context.getString(key), updater)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(
+ key: String,
+ updater: PreferenceUpdater,
+ ): PreferenceScreenManager {
+ updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+ return this
+ }
+
+ /** Adds updater for preference screen. */
+ fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+ screenUpdaters.add(updater)
+ return this
+ }
+
+ /** Adds a list of updaters for preference screen. */
+ fun addPreferenceScreenUpdater(
+ vararg updaters: PreferenceScreenUpdater,
+ ): PreferenceScreenManager {
+ screenUpdaters.addAll(updaters)
+ return this
+ }
+
+ /** Updates preference screen with registered updaters. */
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+ for ((key, updater) in updaters) {
+ preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+ }
+ for (updater in screenUpdaters) {
+ updater.updatePreferenceScreen(preferenceScreen)
+ }
+ }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+ fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 0000000..9e4c1f6
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+ /**
+ * Creates [PreferenceScreen].
+ *
+ * Preference screen creation could happen in background service. The implementation MUST use
+ * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+ */
+ fun createPreferenceScreen(
+ context: Context,
+ preferenceScreenManager: PreferenceScreenManager,
+ ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..5192a9a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorSecondaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..4b16832
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorPrimaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
index 81ddf29..1429e3b 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_accent2_500" android:lStar="51" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
index 037b80a..b46181e 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in light theme -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="98" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
index 762bb31..f0bcf0a 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral2_500" android:lStar="45" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 0000000..4ced9f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
new file mode 100644
index 0000000..230eb7d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurface"/>
+ <item android:color="@color/settingslib_materialColorOnSurface"/>
+</selector>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
new file mode 100644
index 0000000..5bd2a29
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+ <item android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
new file mode 100644
index 0000000..3cb3435
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@android:id/background">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="@color/settingslib_materialColorSurfaceVariant" />
+ </shape>
+ </item>
+
+ <item
+ android:id="@android:id/progress">
+ <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/textColorPrimary" />
+ <size android:width="8dp"/>
+ </shape>
+ </scale>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 0000000..285ab73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 0000000..e417307
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="0dp"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
new file mode 100644
index 0000000..e964657
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="1dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 0000000..a9d69c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomLeftRadius="0dp"
+ android:topRightRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="0dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 5411591..0a36a4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -29,17 +29,20 @@
<color name="settingslib_track_off_color">@android:color/system_neutral1_700</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
<!-- Dialog background color. -->
<color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
<!-- copy from accent_primary_variant_dark_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
@@ -48,7 +51,9 @@
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in dark theme -->
<color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
index beed90e..8cfe54f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -38,7 +38,8 @@
<color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark
</color>
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 229d9e3..7c76ea1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -16,6 +16,34 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
<color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
<color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fe47e85..7706e0e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -35,49 +35,57 @@
<color name="settingslib_track_off_color">@color/settingslib_switch_track_off</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
<!-- Dialog background color -->
<color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
<!-- copy from accent_primary_variant_light_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
-
+ <!--Deprecated. After sdk 35 don't use it.using materialColorOnSurfaceInverse in dark theme-->
<color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceInverse in light theme-->
<color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
<color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
- <color name="settingslib_material_grey_900">#ff212121</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
<color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index e4befc2..a9534c3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+ <item name="preferenceCategoryTitleTextColor">@color/settingslib_text_color_preference_category_title</item>
<item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
index 24e3c46..fb637fb 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
@@ -16,9 +16,11 @@
-->
<resources>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
+ <style name="Theme.SettingsBase_v33" parent="Theme.SettingsBase_v31" >
<item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
<item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v33" />
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
index 3709b5d..185ac3e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -39,8 +39,8 @@
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2691344..2a6499a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -16,12 +16,42 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <!-- Material next track outline color-->
+ <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<!-- The text color of spinner title -->
<color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color>
<!-- The text color of dropdown item title -->
<color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color>
-
<color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
<color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index 01dfd7d..cdd5c25 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -1,31 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<resources>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
- <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
- <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
- <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
-
+ <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <!-- component module background -->
<item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
index 105d1a1..9d477c5 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
index fc845a6..f2e6d2e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index 19c028e..d31f2c4 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
new file mode 100644
index 0000000..e883a4a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+
+/** Sets the content description of this node. */
+fun Modifier.contentDescription(contentDescription: String?) =
+ if (contentDescription != null) this.semantics {
+ this.contentDescription = contentDescription
+ } else this
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index f5cbe8f..d08d97e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -48,10 +48,9 @@
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
@@ -191,8 +190,7 @@
private fun Button(button: CardButton, color: Color) {
TextButton(
onClick = button.onClick,
- modifier =
- Modifier.semantics { button.contentDescription?.let { this.contentDescription = it } }
+ modifier = Modifier.contentDescription(button.contentDescription),
) {
Text(text = button.text, color = color)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 2284436..bc5904c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -40,6 +40,7 @@
singleLine: Boolean = true,
enabled: Boolean = true,
shape: Shape = OutlinedTextFieldDefaults.shape,
+ placeholder: @Composable (() -> Unit)? = null,
modifier: Modifier = Modifier
.fillMaxWidth()
.padding(SettingsDimension.textFieldPadding),
@@ -60,6 +61,7 @@
Text(text = errorMessage)
}
},
+ placeholder = placeholder,
shape = shape
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 56d75d8..23a8e78 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -28,6 +28,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -41,7 +42,8 @@
title: String,
subTitle: @Composable () -> Unit,
modifier: Modifier = Modifier,
- icon: (@Composable () -> Unit)? = null,
+ titleContentDescription: String? = null,
+ icon: @Composable (() -> Unit)? = null,
enabled: () -> Boolean = { true },
paddingStart: Dp = SettingsDimension.itemPaddingStart,
paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
@@ -51,6 +53,7 @@
Row(
modifier = modifier
.fillMaxWidth()
+ .semantics(mergeDescendants = true) {}
.padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
@@ -58,6 +61,7 @@
BaseIcon(icon, alphaModifier, paddingStart)
Titles(
title = title,
+ titleContentDescription = titleContentDescription,
subTitle = subTitle,
modifier = alphaModifier
.weight(1f)
@@ -87,9 +91,14 @@
// Extracts a scope to avoid frequent recompose outside scope.
@Composable
-private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
+private fun Titles(
+ title: String,
+ titleContentDescription: String?,
+ subTitle: @Composable () -> Unit,
+ modifier: Modifier,
+) {
Column(modifier) {
- SettingsTitle(title)
+ SettingsTitle(title, titleContentDescription)
subTitle()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 194ed81..e9b3ba2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -32,6 +32,8 @@
title: String,
summary: () -> String,
modifier: Modifier = Modifier,
+ titleContentDescription: String? = null,
+ summaryContentDescription: () -> String? = { null },
singleLineSummary: Boolean = false,
icon: @Composable (() -> Unit)? = null,
enabled: () -> Boolean = { true },
@@ -42,9 +44,11 @@
) {
BaseLayout(
title = title,
+ titleContentDescription = titleContentDescription,
subTitle = {
SettingsBody(
body = summary(),
+ contentDescription = summaryContentDescription(),
maxLines = if (singleLineSummary) 1 else Int.MAX_VALUE,
)
},
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 3acf075..4ad4c14 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -64,12 +64,24 @@
val title: String
/**
+ * The content description of [title].
+ */
+ val titleContentDescription: String?
+ get() = null
+
+ /**
* The summary of this [Preference].
*/
val summary: () -> String
get() = { "" }
/**
+ * The content description of [summary].
+ */
+ val summaryContentDescription: () -> String?
+ get() = { null }
+
+ /**
* The icon of this [Preference].
*
* Default is `null` which means no icon.
@@ -112,7 +124,9 @@
EntryHighlight {
BasePreference(
title = model.title,
+ titleContentDescription = model.titleContentDescription,
summary = model.summary,
+ summaryContentDescription = model.summaryContentDescription,
singleLineSummary = singleLineSummary,
modifier = modifier,
icon = model.icon,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
index 5155406..2fac576 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -21,8 +21,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.semantics
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
@Composable
@@ -37,9 +36,7 @@
Switch(
checked = checked,
onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
- modifier = if (contentDescription != null) Modifier.semantics {
- this.contentDescription = contentDescription
- } else Modifier,
+ modifier = Modifier.contentDescription(contentDescription),
enabled = changeable(),
interactionSource = interactionSource,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index a59b95a..d423d9f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,16 +30,23 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.toMediumWeight
@Composable
-fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
+fun SettingsTitle(
+ title: String,
+ contentDescription: String? = null,
+ useMediumWeight: Boolean = false,
+) {
Text(
text = title,
- modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ modifier = Modifier
+ .padding(vertical = SettingsDimension.paddingTiny)
+ .contentDescription(contentDescription),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight),
)
@@ -81,11 +88,13 @@
@Composable
fun SettingsBody(
body: String,
+ contentDescription: String? = null,
maxLines: Int = Int.MAX_VALUE,
) {
if (body.isNotEmpty()) {
Text(
text = body,
+ modifier = Modifier.contentDescription(contentDescription),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 8c363db..5ef3329 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -93,11 +94,10 @@
}
}
- val summaryNode = composeTestRule.onNodeWithText(LONG_SUMMARY)
try {
// There is no assertHeightIsAtMost, so use the assertHeightIsAtLeast and catch the
// expected exception.
- summaryNode.assertHeightIsAtLeast(lineHeightDp.times(2))
+ composeTestRule.onRoot().assertHeightIsAtLeast(lineHeightDp.times(5))
} catch (e: AssertionError) {
assertThat(e).hasMessageThat().contains("height")
return
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index f73081a..169c330 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -51,6 +51,7 @@
};
private static final int[] MICROPHONE_OPS = new int[]{
AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
};
private static final int[] CAMERA_OPS = new int[]{
AppOpsManager.OP_CAMERA,
@@ -144,6 +145,11 @@
if (!showSystemApps) {
for (int op : mOps) {
final String permission = AppOpsManager.opToPermission(op);
+ if (permission == null) {
+ // Some ops like OP_PHONE_CALL_MICROPHONE don't have corresponding
+ // permissions. No need to check in this case.
+ continue;
+ }
final int permissionFlags = mPackageManager.getPermissionFlags(permission,
packageName,
user);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 8fd4e91..822a608 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -18,14 +18,18 @@
import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IDeviceIdleController;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.DefaultDialerManager;
import android.text.TextUtils;
@@ -121,6 +125,14 @@
return true;
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+ final int userId = UserHandle.getUserId(uid);
+ if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+ return true;
+ }
+ }
+
return false;
}
@@ -163,27 +175,77 @@
/**
* Add app into power save allow list.
- * @param pkg packageName
+ * @param pkg packageName of the app
*/
+ // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
+ addApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Add app into power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void addApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+
+ if (!wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ true, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
mAllowlistedApps.add(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
/**
* Remove package from power save allow list.
- * @param pkg
+ * @param pkg packageName of the app
*/
public void removeApp(String pkg) {
+ removeApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Remove package from power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void removeApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+ if (wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ false, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
mAllowlistedApps.remove(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 5ed5999..2da6221 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -16,6 +16,8 @@
package com.android.settingslib.utils;
+import static java.lang.Math.abs;
+
import android.content.Context;
import android.icu.text.DateFormat;
import android.icu.text.MeasureFormat;
@@ -212,8 +214,8 @@
* @return The rounded value as a long
*/
public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
- long time = Math.abs(drainTime);
- long multiple = Math.abs(threshold);
+ long time = abs(drainTime);
+ long multiple = abs(threshold);
final long remainder = time % multiple;
if (remainder < multiple / 2) {
return time - remainder;
@@ -222,18 +224,24 @@
}
}
- /** Gets the rounded target time string in a short format. */
+ /** Gets the target time string in a short format. */
public static String getTargetTimeShortString(
Context context, long targetTimeOffsetMs, long currentTimeMs) {
- final long roundedTimeOfDayMs =
- roundTimeToNearestThreshold(
- currentTimeMs + targetTimeOffsetMs, FIFTEEN_MINUTES_MILLIS);
+ long targetTimeMs = currentTimeMs + targetTimeOffsetMs;
+ if (targetTimeOffsetMs >= FIFTEEN_MINUTES_MILLIS) {
+ targetTimeMs = roundUpTimeToNextThreshold(targetTimeMs, FIFTEEN_MINUTES_MILLIS);
+ }
// convert the time to a properly formatted string.
String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
- Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
+ Date date = Date.from(Instant.ofEpochMilli(targetTimeMs));
return fmt.format(date);
}
-}
+ private static long roundUpTimeToNextThreshold(long timeMs, long threshold) {
+ var time = abs(timeMs);
+ var multiple = abs(threshold);
+ return ((time + multiple - 1) / multiple) * multiple;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index b656253..0d318c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -96,7 +96,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.addApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(
new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();
- mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
+ mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index cbc382b..4f3b200 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -87,15 +87,31 @@
}
@Test
- public void getTargetTimeShortString_returnsTimeShortString() {
+ public void getTargetTimeShortString_lessThan15Minutes_returnsTimeShortStringWithoutRounded() {
mContext.getSystemService(AlarmManager.class).setTimeZone("UTC");
mContext.getResources().getConfiguration().setLocale(Locale.US);
var currentTimeMs = Instant.parse("2024-06-06T15:00:00Z").toEpochMilli();
- var remainingTimeMs = Duration.ofMinutes(30).toMillis();
+ var remainingTimeMs = Duration.ofMinutes(15).toMillis() - 1;
var actualTimeString =
PowerUtil.getTargetTimeShortString(mContext, remainingTimeMs, currentTimeMs);
- assertThat(actualTimeString).isEqualTo("3:30 PM");
+ // due to timezone issue in test case, focus on rounded minutes, remove hours part.
+ assertThat(actualTimeString).endsWith("14 PM");
+ }
+
+ @Test
+ public void getTargetTimeShortString_moreThan15Minutes_returnsTimeShortStringWithRounded() {
+ mContext.getSystemService(AlarmManager.class).setTimeZone("UTC");
+ mContext.getResources().getConfiguration().setLocale(Locale.US);
+ var currentTimeMs = Instant.parse("2024-06-06T15:00:00Z").toEpochMilli();
+ var remainingTimeMs = Duration.ofMinutes(15).toMillis() + 1;
+
+ var actualTimeString =
+ PowerUtil.getTargetTimeShortString(mContext, remainingTimeMs, currentTimeMs);
+
+ // due to timezone issue in test case, focus on rounded minutes, remove hours part.
+ assertThat(actualTimeString).endsWith("30 PM");
+
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a33c160..75c0cec 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -272,6 +272,7 @@
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
Settings.Secure.SCREEN_RESOLUTION_MODE,
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1bff592..8faf917 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -430,5 +430,7 @@
VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
+ VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ new InclusiveIntegerRangeValidator(0, 10));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 46cee6b..fa9b279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1760,6 +1760,9 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER_SATURATION_LEVEL);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
SecureSettingsProto.Accessibility.DISPLAY_INVERSION_ENABLED);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b94e224..46bf494 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -934,6 +934,9 @@
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<uses-permission android:name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <uses-permission android:name="android.permission.SETUP_FSVERITY" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c88c373..5e11e1a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,7 @@
"SystemUICustomizationTestUtils",
"androidx.compose.runtime_runtime",
"kosmos",
+ "androidx.test.rules",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c61002e..c2e4b82 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -793,4 +793,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "register_battery_controller_receivers_in_corestartable"
+ namespace: "systemui"
+ description: "Decide whether to register the receivers in battery controller impl in the BatteryControllerStartable corestartable."
+ bug: "307517093"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
new file mode 100644
index 0000000..ffa2b46
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.surfaceeffects.revealeffect
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.RenderEffect
+import androidx.core.graphics.ColorUtils
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.utils.MathUtils
+import kotlin.math.max
+import kotlin.math.min
+
+/** Creates a reveal effect with a circular ripple sparkles on top. */
+class RippleRevealEffect(
+ private val config: RippleRevealEffectConfig,
+ private val renderEffectCallback: RenderEffectDrawCallback,
+ private val stateChangedCallback: AnimationStateChangedCallback? = null
+) {
+ private val rippleRevealShader = RippleRevealShader().apply { applyConfig(config) }
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+ fun play() {
+ if (animator.isRunning) {
+ return
+ }
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val playTime = updateListener.currentPlayTime.toFloat()
+ rippleRevealShader.setTime(playTime * TIME_SCALE_FACTOR)
+
+ // Compute radius.
+ val progress = updateListener.animatedValue as Float
+ val innerRad = MathUtils.lerp(config.innerRadiusStart, config.innerRadiusEnd, progress)
+ val outerRad = MathUtils.lerp(config.outerRadiusStart, config.outerRadiusEnd, progress)
+ rippleRevealShader.setInnerRadius(innerRad)
+ rippleRevealShader.setOuterRadius(outerRad)
+
+ // Compute alphas.
+ val innerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.innerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val outerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.outerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val innerAlpha = MathUtils.lerp(0f, 255f, innerAlphaProgress)
+ val outerAlpha = MathUtils.lerp(0f, 255f, outerAlphaProgress)
+
+ val innerColor = ColorUtils.setAlphaComponent(config.innerColor, innerAlpha.toInt())
+ val outerColor = ColorUtils.setAlphaComponent(config.outerColor, outerAlpha.toInt())
+ rippleRevealShader.setInnerColor(innerColor)
+ rippleRevealShader.setOuterColor(outerColor)
+
+ // Pass in progresses since those functions take in normalized alpha values.
+ rippleRevealShader.setBackgroundAlpha(max(innerAlphaProgress, outerAlphaProgress))
+ rippleRevealShader.setSparkleAlpha(min(innerAlphaProgress, outerAlphaProgress))
+
+ // Trigger draw callback.
+ renderEffectCallback.onDraw(
+ RenderEffect.createRuntimeShaderEffect(
+ rippleRevealShader,
+ RippleRevealShader.BACKGROUND_UNIFORM
+ )
+ )
+ }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ stateChangedCallback?.onAnimationEnd()
+ }
+ }
+ )
+ animator.start()
+ stateChangedCallback?.onAnimationStart()
+ }
+
+ interface AnimationStateChangedCallback {
+ fun onAnimationStart()
+ fun onAnimationEnd()
+ }
+
+ private companion object {
+ private const val TIME_SCALE_FACTOR = 0.00175f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
new file mode 100644
index 0000000..9675f19
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.surfaceeffects.revealeffect
+
+import android.graphics.Color
+
+/** Defines parameters needed for [RippleRevealEffect]. */
+data class RippleRevealEffectConfig(
+ /** Total duration of the animation. */
+ val duration: Float = 0f,
+ /** Timestamp of when the inner mask starts fade out. (Linear fadeout) */
+ val innerFadeOutStart: Float = 0f,
+ /** Timestamp of when the outer mask starts fade out. (Linear fadeout) */
+ val outerFadeOutStart: Float = 0f,
+ /** Center x position of the effect. */
+ val centerX: Float = 0f,
+ /** Center y position of the effect. */
+ val centerY: Float = 0f,
+ /** Start radius of the inner circle. */
+ val innerRadiusStart: Float = 0f,
+ /** End radius of the inner circle. */
+ val innerRadiusEnd: Float = 0f,
+ /** Start radius of the outer circle. */
+ val outerRadiusStart: Float = 0f,
+ /** End radius of the outer circle. */
+ val outerRadiusEnd: Float = 0f,
+ /**
+ * Pixel density of the display. Do not pass a random value. The value must come from
+ * [context.resources.displayMetrics.density].
+ */
+ val pixelDensity: Float = 1f,
+ /**
+ * The amount the circle masks should be softened. Higher value will make the edge of the circle
+ * mask soft.
+ */
+ val blurAmount: Float = 0f,
+ /** Color of the inner circle mask. */
+ val innerColor: Int = Color.WHITE,
+ /** Color of the outer circle mask. */
+ val outerColor: Int = Color.WHITE,
+ /** Multiplier to make the sparkles visible. */
+ val sparkleStrength: Float = SPARKLE_STRENGTH,
+ /** Size of the sparkle. Expected range [0, 1]. */
+ val sparkleScale: Float = SPARKLE_SCALE
+) {
+ /** Default parameters. */
+ companion object {
+ const val SPARKLE_STRENGTH: Float = 0.3f
+ const val SPARKLE_SCALE: Float = 0.8f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
new file mode 100644
index 0000000..a3f9795
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.surfaceeffects.revealeffect
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/** Circular reveal effect with sparkles. */
+class RippleRevealShader : RuntimeShader(SHADER) {
+ // language=AGSL
+ companion object {
+ const val BACKGROUND_UNIFORM = "in_dst"
+ private const val MAIN =
+ """
+ uniform shader ${BACKGROUND_UNIFORM};
+ uniform half in_dstAlpha;
+ uniform half in_time;
+ uniform vec2 in_center;
+ uniform half in_innerRadius;
+ uniform half in_outerRadius;
+ uniform half in_sparkleStrength;
+ uniform half in_blur;
+ uniform half in_pixelDensity;
+ uniform half in_sparkleScale;
+ uniform half in_sparkleAlpha;
+ layout(color) uniform vec4 in_innerColor;
+ layout(color) uniform vec4 in_outerColor;
+
+ vec4 main(vec2 p) {
+ half innerMask = soften(sdCircle(p - in_center, in_innerRadius), in_blur);
+ half outerMask = soften(sdCircle(p - in_center, in_outerRadius), in_blur);
+
+ // Flip it since we are interested in the circle.
+ innerMask = 1.-innerMask;
+ outerMask = 1.-outerMask;
+
+ // Color two circles using the mask.
+ vec4 inColor = vec4(in_innerColor.rgb, 1.) * in_innerColor.a;
+ vec4 outColor = vec4(in_outerColor.rgb, 1.) * in_outerColor.a;
+ vec4 blend = mix(inColor, outColor, innerMask);
+
+ vec4 dst = vec4(in_dst.eval(p).rgb, 1.);
+ dst *= in_dstAlpha;
+
+ blend *= blend.a;
+ // Do normal blend with the background.
+ blend = blend + dst * (1. - blend.a);
+
+ half sparkle =
+ sparkles(p - mod(p, in_pixelDensity * in_sparkleScale), in_time);
+ // Add sparkles using additive blending.
+ blend += sparkle * in_sparkleStrength * in_sparkleAlpha;
+
+ // Mask everything at the end.
+ blend *= outerMask;
+
+ return blend;
+ }
+ """
+
+ private const val SHADER =
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
+ MAIN
+ }
+
+ fun applyConfig(config: RippleRevealEffectConfig) {
+ setCenter(config.centerX, config.centerY)
+ setInnerRadius(config.innerRadiusStart)
+ setOuterRadius(config.outerRadiusStart)
+ setBlurAmount(config.blurAmount)
+ setPixelDensity(config.pixelDensity)
+ setSparkleScale(config.sparkleScale)
+ setSparkleStrength(config.sparkleStrength)
+ setInnerColor(config.innerColor)
+ setOuterColor(config.outerColor)
+ }
+
+ fun setTime(time: Float) {
+ setFloatUniform("in_time", time)
+ }
+
+ fun setCenter(centerX: Float, centerY: Float) {
+ setFloatUniform("in_center", centerX, centerY)
+ }
+
+ fun setInnerRadius(radius: Float) {
+ setFloatUniform("in_innerRadius", radius)
+ }
+
+ fun setOuterRadius(radius: Float) {
+ setFloatUniform("in_outerRadius", radius)
+ }
+
+ fun setBlurAmount(blurAmount: Float) {
+ setFloatUniform("in_blur", blurAmount)
+ }
+
+ fun setPixelDensity(density: Float) {
+ setFloatUniform("in_pixelDensity", density)
+ }
+
+ fun setSparkleScale(scale: Float) {
+ setFloatUniform("in_sparkleScale", scale)
+ }
+
+ fun setSparkleStrength(strength: Float) {
+ setFloatUniform("in_sparkleStrength", strength)
+ }
+
+ fun setInnerColor(color: Int) {
+ setColorUniform("in_innerColor", color)
+ }
+
+ fun setOuterColor(color: Int) {
+ setColorUniform("in_outerColor", color)
+ }
+
+ /** Sets the background alpha. Range [0,1]. */
+ fun setBackgroundAlpha(alpha: Float) {
+ setFloatUniform("in_dstAlpha", alpha)
+ }
+
+ /** Sets the sparkle alpha. Range [0,1]. */
+ fun setSparkleAlpha(alpha: Float) {
+ setFloatUniform("in_sparkleAlpha", alpha)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
new file mode 100644
index 0000000..1411c32
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.surfaceeffects.utils
+
+/** Copied from android.utils.MathUtils */
+object MathUtils {
+ fun constrainedMap(
+ rangeMin: Float,
+ rangeMax: Float,
+ valueMin: Float,
+ valueMax: Float,
+ value: Float
+ ): Float {
+ return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value))
+ }
+
+ fun lerp(start: Float, stop: Float, amount: Float): Float {
+ return start + (stop - start) * amount
+ }
+
+ fun lerpInv(a: Float, b: Float, value: Float): Float {
+ return if (a != b) (value - a) / (b - a) else 0.0f
+ }
+
+ fun saturate(value: Float): Float {
+ return constrain(value, 0.0f, 1.0f)
+ }
+
+ fun lerpInvSat(a: Float, b: Float, value: Float): Float {
+ return saturate(lerpInv(a, b, value))
+ }
+
+ fun constrain(amount: Float, low: Float, high: Float): Float {
+ return if (amount < low) low else if (amount > high) high else amount
+ }
+}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 4cbc18c..f65d797 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -24,10 +24,7 @@
java_library_host {
name: "SystemUILintChecker",
- srcs: [
- "src/**/*.kt",
- "src/**/*.java",
- ],
+ srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
@@ -38,35 +35,13 @@
java_test_host {
name: "SystemUILintCheckerTest",
- srcs: [
- "tests/**/*.kt",
- "tests/**/*.java",
- ],
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
data: [
":framework",
- ":androidx.annotation_annotation",
+ ":androidx.annotation_annotation-nodeps",
],
static_libs: [
"SystemUILintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
index 30e2a25..f9bf306 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
@@ -34,7 +34,6 @@
* Checks if any class has implemented the `Dumpable` interface but has not registered itself with
* the `DumpManager`.
*/
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetector : Detector(), SourceCodeScanner {
private var isDumpable: Boolean = false
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 5840e8f..024c394 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -59,7 +59,7 @@
`BroadcastDispatcher` instead, which registers the receiver on a \
background thread. `BroadcastDispatcher` also improves our visibility \
into ANRs.""",
- moreInfo = "go/identifying-broadcast-threads",
+ moreInfo = "http://go/identifying-broadcast-threads",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 141dd05..f3b24a3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -16,15 +16,8 @@
package com.android.internal.systemui.lint
-import com.android.annotations.NonNull
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
import java.io.File
-import org.intellij.lang.annotations.Language
-
-@Suppress("UnstableApiUsage")
-@NonNull
-private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
@@ -33,16 +26,5 @@
internal val androidStubs =
arrayOf(
LibraryReferenceTestFile(File("framework.jar").canonicalFile),
- LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
- indentedJava(
- """
-package com.android.systemui.settings;
-import android.content.pm.UserInfo;
-
-public interface UserTracker {
- int getUserId();
- UserInfo getUserInfo();
-}
-"""
- ),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation-nodeps.jar").canonicalFile),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 4c4185d..c9bc8b3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -19,17 +19,14 @@
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
- @Ignore
@Test
fun testBindService() {
lint()
@@ -37,7 +34,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
public class TestClass {
public void bind(Context context) {
@@ -48,13 +47,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:9: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
context.bindService(intent, null, 0);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -62,7 +61,6 @@
)
}
- @Ignore
@Test
fun testBindServiceAsUser() {
lint()
@@ -70,7 +68,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
import android.os.UserHandle;
public class TestClass {
@@ -82,13 +82,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:10: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -114,7 +114,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -147,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -181,7 +181,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -219,12 +219,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 30b68f7..3788dda 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
@@ -30,7 +29,6 @@
@Test
fun testSendBroadcast() {
- println(stubs.size)
lint()
.files(
TestFiles.java(
@@ -47,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -80,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -114,7 +112,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -149,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -176,7 +174,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -201,12 +199,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
index ff150c8c..2c20321 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -21,11 +21,8 @@
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
-@Ignore("b/254533331")
class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector {
return CleanArchitectureDependencyViolationDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index ee6e0ce..0652e69 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -24,7 +24,6 @@
import java.util.EnumSet
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
index 3d6cbc7..6c6c263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DumpableNotRegisteredDetector()
@@ -37,7 +36,8 @@
class SomeClass() {
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -67,7 +67,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -97,7 +98,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -127,7 +129,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index ed3d14a..bb34d91 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
@@ -46,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -79,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -104,7 +103,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -136,7 +135,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -149,6 +148,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 846129a..fe5b576 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
@@ -44,7 +43,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -76,7 +75,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -109,7 +108,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -134,7 +133,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -147,6 +146,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 0ac8f8e..3f12569dfc 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
@@ -35,9 +34,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -48,13 +46,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiver(receiver, filter, 0);
~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -69,9 +67,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
@SuppressWarnings("RegisterReceiverViaContext")
public class TestClass {
@@ -83,7 +80,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
@@ -97,11 +94,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -113,13 +107,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -134,11 +128,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -150,19 +141,17 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverForAllUsers(receiver, filter, "permission",
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 34a4249..7944ce3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -182,5 +181,19 @@
.expectClean()
}
- private val stubs = androidStubs
+ private val stubs =
+ arrayOf(
+ *androidStubs,
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ )
+ .indented()
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 34becc6..756751c 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
@@ -45,7 +44,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -80,7 +79,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -102,12 +101,10 @@
}
"""
),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index efe4c90..fd00018 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = StaticSettingsProviderDetector()
@@ -85,7 +84,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
@@ -226,12 +225,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 3f93f07..29b3828 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -10,7 +10,6 @@
import org.junit.runners.JUnit4
import org.junit.runners.model.Statement
-@Suppress("UnstableApiUsage")
@RunWith(JUnit4::class)
abstract class SystemUILintDetectorTest : LintDetectorTest() {
@@ -18,7 +17,7 @@
@ClassRule
@JvmField
val libraryChecker: LibraryExists =
- LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ LibraryExists("framework.jar", "androidx.annotation_annotation-nodeps.jar")
}
class LibraryExists(vararg val libraryNames: String) : TestRule {
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
index db73154..a4e82a7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -18,28 +18,22 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
- override fun getDetector(): Detector {
- return TestFunctionNameViolationDetector()
- }
+ override fun getDetector(): Detector = TestFunctionNameViolationDetector()
- override fun getIssues(): List<Issue> {
- return listOf(
- TestFunctionNameViolationDetector.ISSUE,
- )
- }
+ override fun getIssues(): List<Issue> = listOf(TestFunctionNameViolationDetector.ISSUE)
@Test
fun violations() {
lint()
.files(
- kotlin(
- """
+ TestFiles.kotlin(
+ """
package test.pkg.name
import org.junit.Test
@@ -64,13 +58,11 @@
}
}
"""
- .trimIndent()
- ),
- testAnnotationStub,
+ )
+ .indented(),
+ testAnnotationStub
)
- .issues(
- TestFunctionNameViolationDetector.ISSUE,
- )
+ .issues(TestFunctionNameViolationDetector.ISSUE)
.run()
.expectWarningCount(0)
.expect(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d55d4e4..c22b50d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -21,6 +21,7 @@
import android.app.AlertDialog
import android.content.DialogInterface
import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
@@ -54,6 +55,7 @@
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -66,6 +68,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
@@ -75,6 +78,7 @@
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
import com.android.compose.PlatformButton
+import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -665,10 +669,42 @@
modifier: Modifier = Modifier,
) {
val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+ val appearFadeInAnimatable = remember { Animatable(0f) }
+ val appearMoveAnimatable = remember { Animatable(0f) }
+ val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() }
actionButton?.let { actionButtonViewModel ->
+ LaunchedEffect(Unit) {
+ appearFadeInAnimatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis = 450,
+ delayMillis = 133,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ LaunchedEffect(Unit) {
+ appearMoveAnimatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis = 450,
+ delayMillis = 133,
+ easing = Easings.StandardDecelerate,
+ )
+ )
+ }
+
Box(
- modifier = modifier,
+ modifier =
+ modifier.graphicsLayer {
+ // Translate the button up from an initially pushed-down position:
+ translationY = (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset
+ // Fade the button in:
+ alpha = appearFadeInAnimatable.value
+ },
) {
Button(
onClick = actionButtonViewModel.onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 07c2d3c..19d6038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -52,6 +52,7 @@
import com.android.internal.R
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
@@ -72,15 +73,17 @@
centerDotsVertically: Boolean,
modifier: Modifier = Modifier,
) {
+ val scope = rememberCoroutineScope()
+ val density = LocalDensity.current
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
val colCount = viewModel.columnCount
val rowCount = viewModel.rowCount
val dotColor = MaterialTheme.colorScheme.secondary
- val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() }
+ val dotRadius = with(density) { (DOT_DIAMETER_DP / 2).dp.toPx() }
val lineColor = MaterialTheme.colorScheme.primary
- val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() }
+ val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() }
// All dots that should be rendered on the grid.
val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
@@ -101,7 +104,70 @@
integerResource(R.integer.lock_pattern_line_fade_out_duration)
val lineFadeOutAnimationDelayMs = integerResource(R.integer.lock_pattern_line_fade_out_delay)
- val scope = rememberCoroutineScope()
+ val dotAppearFadeInAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ val dotAppearMoveUpAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ val dotAppearMaxOffsetPixels =
+ remember(dots) {
+ dots.associateWith { dot -> with(density) { (80 + (20 * dot.y)).dp.toPx() } }
+ }
+ val dotAppearScaleAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ LaunchedEffect(Unit) {
+ dotAppearFadeInAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ // Maps a dot at x and y to an ordinal number to denote the order in which all dots
+ // are visited by the fade-in animation.
+ //
+ // The order is basically starting from the top-left most dot (at 0,0) and ending at
+ // the bottom-right most dot (at 2,2). The visitation order happens
+ // diagonal-by-diagonal. Here's a visual representation of the expected output:
+ // [0][1][3]
+ // [2][4][6]
+ // [5][7][8]
+ //
+ // There's an assumption here that the grid is 3x3. If it's not, this formula needs
+ // to be revisited.
+ check(viewModel.columnCount == 3 && viewModel.rowCount == 3)
+ val staggerOrder = max(0, min(8, 2 * (dot.x + dot.y) + (dot.y - 1)))
+
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 33 * staggerOrder,
+ durationMillis = 450,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ }
+ dotAppearMoveUpAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 0,
+ durationMillis = 450 + (33 * dot.y),
+ easing = Easings.StandardDecelerate,
+ )
+ )
+ }
+ }
+ dotAppearScaleAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 33 * dot.y,
+ durationMillis = 450,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ }
+ }
+
val view = LocalView.current
// When the current dot is changed, we need to update our animations.
@@ -322,10 +388,23 @@
// Draw each dot on the grid.
dots.forEach { dot ->
+ val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
+ val appearOffset =
+ (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
drawCircle(
- center = pixelOffset(dot, spacing, horizontalOffset, verticalOffset),
- color = dotColor,
- radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f),
+ center =
+ pixelOffset(
+ dot,
+ spacing,
+ horizontalOffset,
+ verticalOffset + appearOffset,
+ ),
+ color =
+ dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
+ radius =
+ dotRadius *
+ checkNotNull(dotScalingAnimatables[dot]).value *
+ checkNotNull(dotAppearScaleAnimatables[dot]).value,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4533f58..356bfe2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -11,6 +11,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -24,11 +25,10 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
@@ -75,6 +75,7 @@
viewModel: CommunalViewModel,
dataSourceDelegator: SceneDataSourceDelegator,
dialogFactory: SystemUIDialogFactory,
+ colors: CommunalColors,
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
@@ -135,7 +136,7 @@
emptyMap()
},
) {
- CommunalScene(viewModel, dialogFactory, modifier = modifier)
+ CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
}
@@ -143,15 +144,18 @@
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
- viewModel: BaseCommunalViewModel,
+ viewModel: CommunalViewModel,
+ colors: CommunalColors,
dialogFactory: SystemUIDialogFactory,
modifier: Modifier = Modifier,
) {
+ val backgroundColor by colors.backgroundColor.collectAsState()
+
Box(
modifier =
Modifier.element(Communal.Elements.Scrim)
.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant),
+ .background(Color(backgroundColor.toArgb())),
)
Box(modifier.element(Communal.Elements.Content)) {
CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5d87a03..ddfb5f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -250,7 +250,6 @@
)
isDraggingToRemove
},
- onOpenWidgetPicker = onOpenWidgetPicker,
gridState = gridState,
contentListState = contentListState,
selectedKey = selectedKey,
@@ -393,7 +392,6 @@
contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
@@ -461,7 +459,6 @@
model = list[index],
viewModel = viewModel,
size = size,
- onOpenWidgetPicker = onOpenWidgetPicker,
selected = selected && !isDragging,
widgetConfigurator = widgetConfigurator,
)
@@ -739,7 +736,6 @@
size: SizeF,
selected: Boolean,
modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator? = null,
) {
when (model) {
@@ -1145,7 +1141,7 @@
val CardHeightHalf = 282.dp
val CardHeightThird = 177.33.dp
val CardOutlineWidth = 3.dp
- val GridTopSpacing = 72.dp
+ val GridTopSpacing = 64.dp
val GridHeight = CardHeightFull + GridTopSpacing
val Spacing = 16.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index dee2559..37fe798 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@
private var isOnRemoveButton = false
fun onStarted() {
- // assume item will be added to the second to last position before CTA tile.
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
placeHolderIndex = contentListState.list.size - 1
- placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
}
fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 52cbffb..9afb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.composable
import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import dagger.Module
@@ -26,7 +25,6 @@
includes =
[
CommunalBlueprintModule::class,
- DefaultBlueprintModule::class,
OptionalSectionModule::class,
ShortcutsBesideUdfpsBlueprintModule::class,
],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index e499c69..3152535 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -40,9 +40,6 @@
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -230,8 +227,3 @@
}
}
}
-
-@Module
-interface DefaultBlueprintModule {
- @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
index b84b01e..e0bb26e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.keyguard.ui.composable.blueprint
-import javax.inject.Qualifier
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+@Module
+interface DefaultBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612..418c6bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@
internal val scope = SceneScopeImpl(layoutImpl, this)
var content by mutableStateOf(content)
- var userActions by mutableStateOf(actions)
+ private var _userActions by mutableStateOf(checkValid(actions))
var zIndex by mutableFloatStateOf(zIndex)
var targetSize by mutableStateOf(IntSize.Zero)
+ var userActions
+ get() = _userActions
+ set(value) {
+ _userActions = checkValid(value)
+ }
+
+ private fun checkValid(
+ userActions: Map<UserAction, UserActionResult>
+ ): Map<UserAction, UserActionResult> {
+ userActions.forEach { (action, result) ->
+ if (key == result.toScene) {
+ error("Transition to the same scene is not supported. Scene $key, action $action")
+ }
+ }
+ return userActions
+ }
+
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a182..2eaccb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,7 +66,7 @@
private val LayoutSize = 300.dp
}
- private var currentScene by mutableStateOf(TestScenes.SceneA)
+ private var currentScene by mutableStateOf(SceneA)
private lateinit var layoutState: SceneTransitionLayoutState
// We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@
modifier = Modifier.size(LayoutSize),
) {
scene(
- TestScenes.SceneA,
- userActions = mapOf(Back to TestScenes.SceneB),
+ SceneA,
+ userActions = mapOf(Back to SceneB),
) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
Text("SceneA")
}
}
- scene(TestScenes.SceneB) {
+ scene(SceneB) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 100.dp,
@@ -102,7 +106,7 @@
Text("SceneB")
}
}
- scene(TestScenes.SceneC) {
+ scene(SceneC) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 150.dp,
@@ -144,42 +148,42 @@
rule.onNodeWithText("SceneB").assertDoesNotExist()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// Change to scene B. Only that scene is displayed.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.onNodeWithText("SceneA").assertDoesNotExist()
rule.onNodeWithText("SceneB").assertIsDisplayed()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testBack() {
rule.setContent { TestContent() }
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
rule.activity.onBackPressed()
rule.waitForIdle()
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testTransitionState() {
rule.setContent { TestContent() }
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// We will advance the clock manually.
rule.mainClock.autoAdvance = false
// Change the current scene. Until composition is triggered, this won't change the layout
// state.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// On the next frame, we will recompose because currentScene changed, which will start the
// transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
val transition = layoutState.transitionState as TransitionState.Transition
- assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
- assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(transition.fromScene).isEqualTo(SceneA)
+ assertThat(transition.toScene).isEqualTo(SceneB)
assertThat(transition.progress).isEqualTo(0f)
// Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@
// B.
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
@@ -242,7 +246,7 @@
// Go to scene B and let the animation start. See [testLayoutState()] and
// [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
// by 2 frames to be at the start of the animation.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
@@ -251,7 +255,7 @@
// Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
// that B has a higher zIndex than A.
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@
.of(DpOffset(25.dp, 25.dp))
// Animate to scene C, let the animation start then go to the middle of the transition.
- currentScene = TestScenes.SceneC
+ currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@
// Wait for the transition to C to finish.
rule.mainClock.advanceTimeBy(TestTransitionDuration)
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
// Go back to scene A. This should happen instantly (once the animation started, i.e. after
// 2 frames) given that we use a snap() animation spec.
- currentScene = TestScenes.SceneA
+ currentScene = SceneA
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
}
@Test
@@ -346,4 +350,28 @@
)
}
}
+
+ @Test
+ fun userActionFromSceneAToSceneA_throwsNotSupported() {
+ val exception: IllegalStateException =
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ SceneTransitionLayout(
+ state =
+ updateSceneTransitionLayoutState(
+ currentScene = currentScene,
+ onChangeScene = { currentScene = it },
+ transitions = EmptyTestTransitions
+ ),
+ modifier = Modifier.size(LayoutSize),
+ ) {
+ // from SceneA to SceneA
+ scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+ }
+ }
+ }
+
+ assertThat(exception).hasMessageThat().contains(Back.toString())
+ assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 003c572..f1f1b57 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,8 +28,8 @@
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
import android.util.TypedValue
-import android.view.View
import android.view.View.MeasureSpec.EXACTLY
+import android.view.View
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
@@ -88,6 +88,10 @@
private var textAnimator: TextAnimator? = null
private var onTextAnimatorInitialized: Runnable? = null
+ private var translateForCenterAnimation = false
+ private val parentWidth: Int
+ get() = (parent as View).measuredWidth
+
// last text size which is not constrained by view height
private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
@@ -116,14 +120,14 @@
try {
dozingWeightInternal = animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_dozeWeight,
- 100
+ /* default = */ 100
)
lockScreenWeightInternal = animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_lockScreenWeight,
- 300
+ /* default = */ 300
)
chargeAnimationDelay = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_chargeAnimationDelay, 200
+ R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
)
} finally {
animatableClockViewAttributes.recycle()
@@ -134,12 +138,12 @@
defStyleAttr, defStyleRes
)
- isSingleLineInternal =
- try {
- textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
- } finally {
- textViewAttributes.recycle()
- }
+ try {
+ isSingleLineInternal = textViewAttributes.getBoolean(
+ android.R.styleable.TextView_singleLine, /* default = */ false)
+ } finally {
+ textViewAttributes.recycle()
+ }
refreshFormat()
}
@@ -206,6 +210,7 @@
super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
}
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
@@ -215,18 +220,27 @@
} else {
animator.updateLayout(layout)
}
+
if (migratedClocks && hasCustomPositionUpdatedAnimation) {
// Expand width to avoid clock being clipped during stepping animation
- setMeasuredDimension(measuredWidth +
- MeasureSpec.getSize(widthMeasureSpec) / 2, measuredHeight)
+ val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2
+
+ // This comparison is effectively a check if we're in splitshade or not
+ translateForCenterAnimation = parentWidth > targetWidth
+ if (translateForCenterAnimation) {
+ setMeasuredDimension(targetWidth, measuredHeight)
+ }
+ } else {
+ translateForCenterAnimation = false
}
}
override fun onDraw(canvas: Canvas) {
- if (migratedClocks && hasCustomPositionUpdatedAnimation) {
- canvas.save()
- canvas.translate((parent as View).measuredWidth / 4F, 0F)
+ canvas.save()
+ if (translateForCenterAnimation) {
+ canvas.translate(parentWidth / 4f, 0f)
}
+
logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
// Use textAnimator to render text if animation is enabled.
// Otherwise default to using standard draw functions.
@@ -236,9 +250,8 @@
} else {
super.onDraw(canvas)
}
- if (migratedClocks && hasCustomPositionUpdatedAnimation) {
- canvas.restore()
- }
+
+ canvas.restore()
}
override fun invalidate() {
@@ -527,9 +540,9 @@
* means it finished moving.
*/
fun offsetGlyphsForStepClockAnimation(
- clockStartLeft: Int,
- clockMoveDirection: Int,
- moveFraction: Float
+ clockStartLeft: Int,
+ clockMoveDirection: Int,
+ moveFraction: Float
) {
val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
val currentMoveAmount = left - clockStartLeft
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 3889703..e332656 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -18,9 +18,6 @@
import android.os.VibrationEffect
import android.testing.TestableLooper.RunWithLooper
-import android.view.MotionEvent
-import android.view.View
-import androidx.test.core.view.MotionEventBuilder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,18 +26,15 @@
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -50,7 +44,6 @@
class QSLongPressEffectTest : SysuiTestCase() {
@Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var testView: View
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private val kosmos = testKosmos()
private val vibratorHelper = kosmos.vibratorHelper
@@ -73,7 +66,6 @@
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardInteractor,
- CoroutineScope(kosmos.backgroundCoroutineContext),
)
longPressEffect.initializeEffect(effectDuration)
}
@@ -133,8 +125,7 @@
@Test
fun onActionDown_whileIdle_startsWait() = testWithScope {
// GIVEN an action down event occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect moves to the TIMEOUT_WAIT state
val state by collectLastValue(longPressEffect.state)
@@ -144,8 +135,7 @@
@Test
fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
// GIVEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect goes back to idle and does not start
val state by collectLastValue(longPressEffect.state)
@@ -159,8 +149,7 @@
val action by collectLastValue(longPressEffect.actionType)
// GIVEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the action to invoke is the click action and the effect does not start
assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
@@ -182,8 +171,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -195,8 +183,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -230,12 +217,10 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN an action down occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect resets
assertEffectResets()
@@ -247,8 +232,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN that the animation completes after a sufficient amount of time
animatorTestRule.advanceTimeBy(effectDuration.toLong())
@@ -258,9 +242,6 @@
assertThat(state).isEqualTo(QSLongPressEffect.State.IDLE)
}
- private fun buildMotionEvent(action: Int): MotionEvent =
- MotionEventBuilder.newBuilder().setAction(action).build()
-
private fun testWithScope(test: suspend TestScope.() -> Unit) =
with(kosmos) { testScope.runTest { test() } }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index f0498de..1501d9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -51,7 +51,6 @@
statusBarStateController = statusBarStateController,
mainExecutor = mainExecutor,
legacyActivityStarter = { legacyActivityStarterInternal },
- activityStarterInternal = { activityStarterInternal },
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 106b548..97c8d5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index d06a6e2..a6fdd03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,14 +63,21 @@
private lateinit var demoImpl: DemoWifiRepository
@Mock private lateinit var demoModeController: DemoModeController
- @Mock private lateinit var logger: WifiInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val featureFlags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -78,17 +89,18 @@
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
+ whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+
realImpl =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- FakeConnectivityRepository(),
- logger,
- tableLogger,
+ featureFlags,
+ testScope.backgroundScope,
mainExecutor,
testDispatcher,
- testScope.backgroundScope,
+ wifiPickerTrackerFactory,
wifiManager,
+ wifiLogBuffer,
+ tableLogger,
)
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
deleted file mode 100644
index cf20ba8..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ /dev/null
@@ -1,1323 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
-
-import android.content.Intent
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_VPN
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.TransportInfo
-import android.net.VpnTransportInfo
-import android.net.vcn.VcnTransportInfo
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiInfo
-import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
-import android.net.wifi.WifiManager.UNKNOWN_SSID
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class WifiRepositoryImplTest : SysuiTestCase() {
-
- private lateinit var underTest: WifiRepositoryImpl
-
- @Mock private lateinit var logger: WifiInputLogger
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var wifiManager: WifiManager
- private lateinit var executor: Executor
- private lateinit var connectivityRepository: ConnectivityRepository
-
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- executor = FakeExecutor(FakeSystemClock())
-
- connectivityRepository =
- ConnectivityRepositoryImpl(
- connectivityManager,
- ConnectivitySlots(context),
- context,
- mock(),
- mock(),
- testScope.backgroundScope,
- mock(),
- )
-
- underTest = createRepo()
- }
-
- @Test
- fun isWifiEnabled_initiallyGetsWifiManagerValue() =
- testScope.runTest {
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
-
- underTest = createRepo()
- testScope.runCurrent()
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
- }
-
- @Test
- fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_networkLost_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_intentsReceived_valueUpdated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_initiallyGetsDefault() =
- testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
-
- @Test
- fun isWifiDefault_wifiNetwork_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
-
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isFalse()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_wifiNetworkLost_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- // First, add a network
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isTrue()
-
- // WHEN the network is lost
- getDefaultNetworkCallback().onLost(NETWORK)
-
- // THEN we update to false
- assertThat(latest).isFalse()
- }
-
- @Test
- fun wifiNetwork_initiallyGetsDefault() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
- }
-
- @Test
- fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_neverHasHotspot() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
- .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val rssi = -57
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.rssi).thenReturn(rssi)
- whenever(this.subscriptionId).thenReturn(567)
- }
-
- whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
- whenever(wifiManager.maxSignalLevel).thenReturn(5)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
- assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
- assertThat(latestCarrierMerged.level).isEqualTo(2)
- // numberOfLevels = maxSignalLevel + 1
- assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
- }
-
- @Test
- fun wifiNetwork_notValidated_networkNotValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_validated_networkValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
- }
-
- @Test
- fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we update to a new primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we use the new network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(newNetworkId)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- }
-
- @Test
- fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we notify of a new but non-primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "EF"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we still use the original network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo, isValidated = true)
- )
-
- // WHEN we keep the same network ID but change the SSID
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
- )
-
- // THEN we've updated to the new SSID
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- assertThat(latestActive.isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
- getNetworkCallback().onLost(NETWORK)
-
- // THEN there's no crash and we still have no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Possible regression test for b/278618530. */
- @Test
- fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- assertThat((latest as WifiNetworkModel.CarrierMerged).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose an unknown network
- val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) }
- getNetworkCallback().onLost(unknownNetwork)
-
- // THEN we still have our previous network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we update to a new network...
- val newNetworkId = 89
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- // ...and lose the old network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we still have the new network
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
- }
-
- /** Regression test for b/244173280. */
- @Test
- fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
- testScope.runTest {
- val latest1 by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest1 is WifiNetworkModel.Active).isTrue()
- val latest1Active = latest1 as WifiNetworkModel.Active
- assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest1Active.ssid).isEqualTo(SSID)
-
- // WHEN we add a second subscriber after having already emitted a value
- val latest2 by collectLastValue(underTest.wifiNetwork)
-
- // THEN the second subscribe receives the already-emitted value
- assertThat(latest2 is WifiNetworkModel.Active).isTrue()
- val latest2Active = latest2 as WifiNetworkModel.Active
- assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest2Active.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun secondaryNetworks_alwaysEmpty() =
- testScope.runTest {
- val latest by collectLastValue(underTest.secondaryNetworks)
- collectLastValue(underTest.wifiNetwork)
-
- // Even WHEN we do have non-primary wifi info
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- // THEN the secondary networks list is empty because this repo doesn't support it
- assertThat(latest).isEmpty()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- // A non-primary network is inactive
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(null)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(UNKNOWN_SSID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- // Start with active
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
- // WHEN the network is lost
- getNetworkCallback().onLost(NETWORK)
- testScope.runCurrent()
-
- // THEN the isWifiConnected updates
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
- }
-
- @Test
- fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
- }
-
- @Test
- fun wifiScanResults_containsSsidList() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- val scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- val expected =
- listOf(
- WifiScanEntry(ssid = "ssid 1"),
- WifiScanEntry(ssid = "ssid 2"),
- WifiScanEntry(ssid = "ssid 3"),
- WifiScanEntry(ssid = "ssid 4"),
- WifiScanEntry(ssid = "ssid 5"),
- )
-
- assertThat(latest).isEqualTo(expected)
- }
-
- @Test
- fun wifiScanResults_updates() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- var scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- // New scan representing no results
- scanResults = emptyList()
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- assertThat(latest).isEmpty()
- }
-
- private fun createRepo(): WifiRepositoryImpl {
- return WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- tableLogger,
- executor,
- dispatcher,
- testScope.backgroundScope,
- wifiManager,
- )
- }
-
- private fun getTrafficStateCallback(): TrafficStateCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<TrafficStateCallback>()
- verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
- verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun createWifiNetworkCapabilities(
- transportInfo: TransportInfo,
- isValidated: Boolean = true,
- ): NetworkCapabilities {
- return mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(transportInfo)
- whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
- }
- }
-
- private companion object {
- const val NETWORK_ID = 45
- val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
- const val SSID = "AB"
- val PRIMARY_WIFI_INFO: WifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
new file mode 100644
index 0000000..55e46dc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.wmshell
+
+import android.content.pm.UserInfo
+import android.graphics.Color
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
+import com.android.systemui.communal.util.fakeCommunalColors
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sysUiState
+import com.android.systemui.notetask.NoteTaskInitializer
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.wm.shell.desktopmode.DesktopMode
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.onehanded.OneHanded
+import com.android.wm.shell.onehanded.OneHandedEventCallback
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback
+import com.android.wm.shell.pip.Pip
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.sysui.ShellInterface
+import java.util.Optional
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [WMShell].
+ *
+ * Build/Install/Run: atest SystemUITests:WMShellTest
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WMShellTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ @Mock private lateinit var mShellInterface: ShellInterface
+ @Mock private lateinit var mScreenLifecycle: ScreenLifecycle
+ @Mock private lateinit var mPip: Pip
+ @Mock private lateinit var mSplitScreen: SplitScreen
+ @Mock private lateinit var mOneHanded: OneHanded
+ @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer
+ @Mock private lateinit var mDesktopMode: DesktopMode
+ @Mock private lateinit var mRecentTasks: RecentTasks
+
+ private val mCommandQueue: CommandQueue = kosmos.commandQueue
+ private val mConfigurationController: ConfigurationController = kosmos.configurationController
+ private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+ private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val mSysUiState: SysUiState = kosmos.sysUiState
+ private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle
+ private val mUserTracker: UserTracker = kosmos.userTracker
+ private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor
+ private val communalTransitionViewModel = kosmos.communalTransitionViewModel
+
+ private lateinit var underTest: WMShell
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val displayTracker = FakeDisplayTracker(mContext)
+
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+ underTest =
+ WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
+ Optional.of(mDesktopMode),
+ Optional.of(mRecentTasks),
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ displayTracker,
+ mNoteTaskInitializer,
+ communalTransitionViewModel,
+ JavaAdapter(testScope.backgroundScope),
+ mSysUiMainExecutor
+ )
+ }
+
+ @Test
+ fun initPip_registersCommandQueueCallback() {
+ underTest.initPip(mPip)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ }
+
+ @Test
+ fun initOneHanded_registersCallbacks() {
+ underTest.initOneHanded(mOneHanded)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java))
+ verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java))
+ verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java))
+ }
+
+ @Test
+ fun initDesktopMode_registersListener() {
+ underTest.initDesktopMode(mDesktopMode)
+ verify(mDesktopMode)
+ .addVisibleTasksListener(
+ any(VisibleTasksListener::class.java),
+ any(Executor::class.java)
+ )
+ }
+
+ @Test
+ fun initRecentTasks_registersListener() {
+ underTest.initRecentTasks(mRecentTasks)
+ verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() =
+ testScope.runTest {
+ val black = Color.valueOf(Color.BLACK)
+ kosmos.fakeCommunalColors.setBackgroundColor(black)
+
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
+
+ underTest.initRecentTasks(mRecentTasks)
+ runCurrent()
+ verify(mRecentTasks).setTransitionBackgroundColor(null)
+ verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
+
+ setDocked(true)
+ // Make communal available
+ kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+
+ runCurrent()
+
+ verify(mRecentTasks).setTransitionBackgroundColor(black)
+ }
+
+ private fun TestScope.setDocked(docked: Boolean) {
+ kosmos.fakeDockManager.setIsDocked(docked)
+ val event =
+ if (docked) {
+ DockManager.STATE_DOCKED
+ } else {
+ DockManager.STATE_NONE
+ }
+ kosmos.fakeDockManager.setDockEvent(event)
+ runCurrent()
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
similarity index 68%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
rename to packages/SystemUI/res/color/notification_focus_overlay_color.xml
index fff41c3..6a3c7a1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
+++ b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
@@ -14,11 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
- </style>
-</resources>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_focused="true" android:color="?androidprv:attr/materialColorSecondary" />
+ <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
new file mode 100644
index 0000000..6e6e032
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/textColorTertiary" />
+ <solid android:color="@android:color/transparent"/>
+ </shape>
+ </item>
+ <item
+ android:end="20dp"
+ android:gravity="end|center_vertical">
+ <vector
+ android:width="@dimen/screenrecord_spinner_arrow_size"
+ android:height="@dimen/screenrecord_spinner_arrow_size"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?androidprv:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
new file mode 100644
index 0000000..f35975e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 3f903ae..587a5a0 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -28,4 +28,9 @@
<solid android:color="@color/notification_state_color_default" />
</shape>
</item>
+ <item>
+ <shape>
+ <stroke android:width="3dp" android:color="@color/notification_focus_overlay_color"/>
+ </shape>
+ </item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index a5cdaeb..8e1d0a5 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -17,6 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/root"
style="@style/Widget.SliceView.Panel"
android:layout_width="wrap_content"
@@ -26,9 +27,33 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toTopOf="@id/pair_new_device_button" />
+ app:layout_constraintBottom_toTopOf="@id/preset_spinner" />
+
+ <Spinner
+ android:id="@+id/preset_spinner"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/hearing_devices_preset_spinner_height"
+ android:layout_marginTop="@dimen/hearing_devices_preset_spinner_margin"
+ android:layout_marginBottom="@dimen/hearing_devices_preset_spinner_margin"
+ android:gravity="center_vertical"
+ android:background="@drawable/hearing_devices_preset_spinner_background"
+ android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
+ android:visibility="gone"/>
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/device_barrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="device_list,preset_spinner" />
<Button
android:id="@+id/pair_new_device_button"
@@ -41,7 +66,7 @@
android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintTop_toBottomOf="@id/device_barrier"
android:drawableStart="@drawable/ic_add"
android:drawablePadding="20dp"
android:drawableTint="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 26fa2b1..82395e4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1762,6 +1762,14 @@
<!-- The height of the main scroll view in bluetooth dialog with auto on toggle. -->
<dimen name="bluetooth_dialog_scroll_view_min_height_with_auto_on">350dp</dimen>
+ <!-- Hearing devices dialog related dimensions -->
+ <dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_margin">24dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_text_padding_end">80dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
+
<!-- Height percentage of the parent container occupied by the communal view -->
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4690f02..3e13043 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -916,6 +916,8 @@
<string name="quick_settings_pair_hearing_devices">Pair new device</string>
<!-- QuickSettings: Content description of the hearing devices dialog pair new device [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pair_new_device">Click to pair new device</string>
+ <!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
new file mode 100644
index 0000000..349964b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.dagger.DeviceEntryIconLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val GENERIC_TAG = "DeviceEntryIconLogger"
+
+/** Helper class for logging for the DeviceEntryIcon */
+@SysUISingleton
+class DeviceEntryIconLogger
+@Inject
+constructor(@DeviceEntryIconLog private val logBuffer: LogBuffer) {
+ fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) =
+ logBuffer.log(GENERIC_TAG, level, msg)
+
+ fun logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+ shouldHandleTouches: Boolean,
+ canTouchDeviceEntryViewAlpha: Boolean,
+ alternateBouncerVisible: Boolean,
+ hideAffordancesRequest: Boolean,
+ ) {
+ logBuffer.log(
+ "DeviceEntryUdfpsTouchOverlay",
+ DEBUG,
+ {
+ bool1 = canTouchDeviceEntryViewAlpha
+ bool2 = alternateBouncerVisible
+ bool3 = hideAffordancesRequest
+ bool4 = shouldHandleTouches
+ },
+ {
+ "shouldHandleTouches=$bool4 canTouchDeviceEntryViewAlpha=$bool1 " +
+ "alternateBouncerVisible=$bool2 hideAffordancesRequest=$bool3"
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index af8149f..0bd6d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -18,8 +18,15 @@
import static android.view.WindowManager.LayoutParams;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
@@ -30,56 +37,123 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
-class FullscreenMagnificationController {
+class FullscreenMagnificationController implements ComponentCallbacks {
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
private Supplier<SurfaceControlViewHost> mScvhSupplier;
- private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControlViewHost mSurfaceControlViewHost = null;
+ private SurfaceControl mBorderSurfaceControl = null;
private Rect mWindowBounds;
private SurfaceControl.Transaction mTransaction;
private View mFullscreenBorder = null;
private int mBorderOffset;
private final int mDisplayId;
private static final Region sEmptyRegion = new Region();
+ private ValueAnimator mShowHideBorderAnimator;
+ private Executor mExecutor;
+ private boolean mFullscreenMagnificationActivated = false;
+ private final Configuration mConfiguration;
FullscreenMagnificationController(
@UiContext Context context,
+ Executor executor,
AccessibilityManager accessibilityManager,
WindowManager windowManager,
Supplier<SurfaceControlViewHost> scvhSupplier) {
+ this(context, executor, accessibilityManager, windowManager, scvhSupplier,
+ new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+ }
+
+ @VisibleForTesting
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ @Main Executor executor,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SurfaceControl.Transaction transaction,
+ ValueAnimator valueAnimator) {
mContext = context;
+ mExecutor = executor;
mAccessibilityManager = accessibilityManager;
mWindowManager = windowManager;
mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- mTransaction = new SurfaceControl.Transaction();
+ mTransaction = transaction;
mScvhSupplier = scvhSupplier;
mBorderOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen_with_offset)
- mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen);
mDisplayId = mContext.getDisplayId();
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mShowHideBorderAnimator = valueAnimator;
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ // The animation was played in reverse, which means we are hiding the border.
+ // We would like to perform clean up after the border is fully hidden.
+ cleanUpBorder();
+ }
+ }
+ });
}
+ private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+ final ValueAnimator valueAnimator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ valueAnimator.setInterpolator(interpolator);
+ valueAnimator.setDuration(longAnimationDuration);
+ return valueAnimator;
+ }
+
+ /**
+ * Check the fullscreen magnification activation status, and proceed corresponding actions when
+ * there is an activation change.
+ */
@UiThread
void onFullscreenMagnificationActivationChanged(boolean activated) {
- if (activated) {
- createFullscreenMagnificationBorder();
- } else {
- removeFullscreenMagnificationBorder();
+ final boolean changed = (mFullscreenMagnificationActivated != activated);
+ if (changed) {
+ mFullscreenMagnificationActivated = activated;
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
}
}
+ /**
+ * This method should only be called when fullscreen magnification is changed from activated
+ * to inactivated.
+ */
@UiThread
private void removeFullscreenMagnificationBorder() {
+ mContext.unregisterComponentCallbacks(this);
+ mShowHideBorderAnimator.reverse();
+ }
+
+ private void cleanUpBorder() {
if (mSurfaceControlViewHost != null) {
mSurfaceControlViewHost.release();
mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@
}
/**
- * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
- * and set negative offset to the border view to fill up the spaces between the border and the
- * device corners.
+ * This method should only be called when fullscreen magnification is changed from inactivated
+ * to activated.
*/
@UiThread
private void createFullscreenMagnificationBorder() {
- mFullscreenBorder = LayoutInflater.from(mContext)
- .inflate(R.layout.fullscreen_magnification_border, null);
- mSurfaceControlViewHost = mScvhSupplier.get();
- mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ onConfigurationChanged(mContext.getResources().getConfiguration());
+ mContext.registerComponentCallbacks(this);
- SurfaceControl surfaceControl = mSurfaceControlViewHost
- .getSurfacePackage().getSurfaceControl();
+ if (mSurfaceControlViewHost == null) {
+ // Create the view only if it does not exist yet. If we are trying to enable fullscreen
+ // magnification before it was fully disabled, we use the previous view instead of
+ // creating a new one.
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ // Set the initial border view alpha manually so we won't show the border accidentally
+ // after we apply show() to the SurfaceControl and before the animation starts to run.
+ mFullscreenBorder.setAlpha(0f);
+ mShowHideBorderAnimator.setTarget(mFullscreenBorder);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+ }
mTransaction
- .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
- .setLayer(surfaceControl, Integer.MAX_VALUE)
- .show(surfaceControl)
+ .addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ if (mShowHideBorderAnimator.isRunning()) {
+ // Since the method is only called when there is an activation
+ // status change, the running animator is hiding the border.
+ mShowHideBorderAnimator.reverse();
+ } else {
+ mShowHideBorderAnimator.start();
+ }
+ })
+ .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
+ .show(mBorderSurfaceControl)
.apply();
- mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(
+ mDisplayId, mBorderSurfaceControl);
applyTouchableRegion();
}
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
private LayoutParams getBorderLayoutParams() {
LayoutParams params = new LayoutParams(
mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@
// all touch events to go through this view.
surfaceControl.setTouchableRegion(sEmptyRegion);
}
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ onConfigurationChanged(configDiff);
+ }
+
+ @VisibleForTesting
+ void onConfigurationChanged(int configDiff) {
+ boolean reCreateWindow = false;
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+ || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
+ || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ updateDimensions();
+ mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
+ reCreateWindow = true;
+ }
+
+ if (mFullscreenBorder != null && reCreateWindow) {
+ final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
+ final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
+ mSurfaceControlViewHost.relayout(newWidth, newHeight);
+ }
+ }
+
+ private void updateDimensions() {
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ }
+
+ @Override
+ public void onLowMemory() {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 70165f3..177d933 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -54,6 +54,7 @@
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -71,6 +72,7 @@
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
private final Handler mHandler;
+ private final Executor mExecutor;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@
DisplayIdIndexSupplier<FullscreenMagnificationController> {
private final Context mContext;
+ private final Executor mExecutor;
- FullscreenMagnificationControllerSupplier(Context context, Handler handler,
- DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings) {
+ FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
+ Executor executor) {
super(displayManager);
mContext = context;
+ mExecutor = executor;
}
@Override
@@ -156,6 +159,7 @@
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new FullscreenMagnificationController(
windowContext,
+ mExecutor,
windowContext.getSystemService(AccessibilityManager.class),
windowContext.getSystemService(WindowManager.class),
scvhSupplier);
@@ -200,13 +204,14 @@
DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
@Inject
- public Magnification(Context context, @Main Handler mainHandler,
+ public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
mHandler = mainHandler;
+ mExecutor = executor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, mHandler, displayManager, sysUiState, secureSettings);
+ context, displayManager, mExecutor);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 475bb2c..7b5a09c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -21,6 +21,8 @@
import static java.util.Collections.emptyList;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
@@ -30,7 +32,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.Visibility;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -40,7 +46,9 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
@@ -48,7 +56,9 @@
import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -80,11 +90,37 @@
private final LocalBluetoothManager mLocalBluetoothManager;
private final Handler mMainHandler;
private final AudioManager mAudioManager;
-
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final HapClientProfile mHapClientProfile;
private HearingDevicesListAdapter mDeviceListAdapter;
+ private HearingDevicesPresetsController mPresetsController;
+ private Context mApplicationContext;
private SystemUIDialog mDialog;
private RecyclerView mDeviceList;
+ private List<DeviceItem> mHearingDeviceItemList;
+ private Spinner mPresetSpinner;
+ private ArrayAdapter<String> mPresetInfoAdapter;
private Button mPairButton;
+ private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
+ new HearingDevicesPresetsController.PresetCallback() {
+ @Override
+ public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
+ int activePresetIndex) {
+ mMainHandler.post(
+ () -> refreshPresetInfoAdapter(presetInfos, activePresetIndex));
+ }
+
+ @Override
+ public void onPresetCommandFailed(int reason) {
+ final List<BluetoothHapPresetInfo> presetInfos =
+ mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ mMainHandler.post(() -> {
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ showPresetErrorToast(mApplicationContext);
+ });
+ }
+ };
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -107,6 +143,7 @@
@AssistedInject
public HearingDevicesDialogDelegate(
+ @Application Context applicationContext,
@Assisted boolean showPairNewDevice,
SystemUIDialog.Factory systemUIDialogFactory,
ActivityStarter activityStarter,
@@ -114,6 +151,7 @@
@Nullable LocalBluetoothManager localBluetoothManager,
@Main Handler handler,
AudioManager audioManager) {
+ mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -121,6 +159,8 @@
mLocalBluetoothManager = localBluetoothManager;
mMainHandler = handler;
mAudioManager = audioManager;
+ mProfileManager = localBluetoothManager.getProfileManager();
+ mHapClientProfile = mProfileManager.getHapClientProfile();
}
@Override
@@ -158,19 +198,34 @@
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ CachedBluetoothDevice activeHearingDevice;
+ mHearingDeviceItemList = getHearingDevicesList();
+ if (mPresetsController != null) {
+ activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
+ mPresetsController.setActiveHearingDevice(activeHearingDevice);
+ } else {
+ activeHearingDevice = null;
+ }
+ mMainHandler.post(() -> {
+ mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ mPresetSpinner.setVisibility(
+ (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE
+ : GONE);
+ });
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ mHearingDeviceItemList = getHearingDevicesList();
+ mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ mHearingDeviceItemList = getHearingDevicesList();
+ mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
}
@Override
@@ -187,10 +242,15 @@
@Override
public void onCreate(@NonNull SystemUIDialog dialog, @Nullable Bundle savedInstanceState) {
+ if (mLocalBluetoothManager == null) {
+ return;
+ }
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
+ mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
setupDeviceListView(dialog);
+ setupPresetSpinner(dialog);
setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE);
}
@@ -199,7 +259,14 @@
if (mLocalBluetoothManager == null) {
return;
}
+
mLocalBluetoothManager.getEventManager().registerCallback(this);
+ if (mPresetsController != null) {
+ mPresetsController.registerHapCallback();
+ if (mHapClientProfile != null && !mHapClientProfile.isProfileReady()) {
+ mProfileManager.addServiceListener(mPresetsController);
+ }
+ }
}
@Override
@@ -207,15 +274,51 @@
if (mLocalBluetoothManager == null) {
return;
}
+
+ if (mPresetsController != null) {
+ mPresetsController.unregisterHapCallback();
+ mProfileManager.removeServiceListener(mPresetsController);
+ }
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mDeviceListAdapter = new HearingDevicesListAdapter(getHearingDevicesList(), this);
+ mHearingDeviceItemList = getHearingDevicesList();
+ mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
mDeviceList.setAdapter(mDeviceListAdapter);
}
+ private void setupPresetSpinner(SystemUIDialog dialog) {
+ mPresetsController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
+ mHearingDeviceItemList);
+ mPresetsController.setActiveHearingDevice(activeHearingDevice);
+
+ mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
+ android.R.layout.simple_spinner_dropdown_item);
+ mPresetInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mPresetSpinner.setAdapter(mPresetInfoAdapter);
+ mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mPresetsController.selectPreset(
+ mPresetsController.getAllPresetInfo().get(position).getIndex());
+ mPresetSpinner.setSelection(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
+ }
+ });
+ final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ mPresetSpinner.setVisibility(
+ (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+ }
+
private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
if (visibility == VISIBLE) {
mPairButton.setOnClickListener(v -> {
@@ -230,6 +333,21 @@
}
}
+ private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos,
+ int activePresetIndex) {
+ mPresetInfoAdapter.clear();
+ mPresetInfoAdapter.addAll(
+ presetInfos.stream().map(BluetoothHapPresetInfo::getName).toList());
+ if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
+ final int size = mPresetInfoAdapter.getCount();
+ for (int position = 0; position < size; position++) {
+ if (presetInfos.get(position).getIndex() == activePresetIndex) {
+ mPresetSpinner.setSelection(position);
+ }
+ }
+ }
+ }
+
private List<DeviceItem> getHearingDevicesList() {
if (mLocalBluetoothManager == null
|| !mLocalBluetoothManager.getBluetoothAdapter().isEnabled()) {
@@ -242,6 +360,15 @@
.collect(Collectors.toList());
}
+ @Nullable
+ private CachedBluetoothDevice getActiveHearingDevice(List<DeviceItem> hearingDeviceItemList) {
+ return hearingDeviceItemList.stream()
+ .filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+ .map(DeviceItem::getCachedBluetoothDevice)
+ .findFirst()
+ .orElse(null);
+ }
+
private DeviceItem createHearingDeviceItem(CachedBluetoothDevice cachedDevice) {
final Context context = mDialog.getContext();
if (cachedDevice == null) {
@@ -260,4 +387,8 @@
mDialog.dismiss();
}
}
+
+ private void showPresetErrorToast(Context context) {
+ Toast.makeText(context, R.string.hearing_devices_presets_error, Toast.LENGTH_SHORT).show();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
new file mode 100644
index 0000000..02fa003
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.hearingaid;
+
+import static java.util.Collections.emptyList;
+
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.List;
+
+/**
+ * The controller of the hearing devices presets of the bluetooth Hearing Access Profile.
+ */
+public class HearingDevicesPresetsController implements
+ LocalBluetoothProfileManager.ServiceListener, BluetoothHapClient.Callback {
+
+ private static final String TAG = "HearingDevicesPresetsController";
+ private static final boolean DEBUG = true;
+
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final HapClientProfile mHapClientProfile;
+ private final PresetCallback mPresetCallback;
+
+ private CachedBluetoothDevice mActiveHearingDevice;
+ private int mSelectedPresetIndex;
+
+ public HearingDevicesPresetsController(LocalBluetoothProfileManager profileManager,
+ PresetCallback presetCallback) {
+ mProfileManager = profileManager;
+ mHapClientProfile = mProfileManager.getHapClientProfile();
+ mPresetCallback = presetCallback;
+ }
+
+ @Override
+ public void onServiceConnected() {
+ if (mHapClientProfile != null && mHapClientProfile.isProfileReady()) {
+ mProfileManager.removeServiceListener(this);
+ registerHapCallback();
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ // Do nothing
+ }
+
+ @Override
+ public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (DEBUG) {
+ Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ + ", presetIndex: " + presetIndex + ", reason: " + reason);
+ }
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onPresetInfoChanged(@NonNull BluetoothDevice device,
+ @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (DEBUG) {
+ Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress()
+ + ", reason: " + reason + ", infoList: " + presetInfoList);
+ }
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress()
+ + ", reason: " + reason);
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+ }
+
+ @Override
+ public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ + ", reason: " + reason);
+ selectPresetIndependently(mSelectedPresetIndex);
+ }
+ }
+
+ @Override
+ public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress()
+ + ", reason: " + reason);
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+ }
+
+ @Override
+ public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ + ", reason: " + reason);
+ }
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+
+ /**
+ * Registers a callback to be notified about operation changed for {@link HapClientProfile}.
+ */
+ public void registerHapCallback() {
+ if (mHapClientProfile != null) {
+ try {
+ mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+ } catch (IllegalArgumentException e) {
+ // The callback was already registered
+ Log.w(TAG, "Cannot register callback: " + e.getMessage());
+ }
+
+ }
+ }
+
+ /**
+ * Removes a previously-added {@link HapClientProfile} callback.
+ */
+ public void unregisterHapCallback() {
+ if (mHapClientProfile != null) {
+ try {
+ mHapClientProfile.unregisterCallback(this);
+ } catch (IllegalArgumentException e) {
+ // The callback was never registered or was already unregistered
+ Log.w(TAG, "Cannot unregister callback: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Sets the hearing device for this controller to control the preset.
+ *
+ * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
+ */
+ public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) {
+ mActiveHearingDevice = activeHearingDevice;
+ }
+
+ /**
+ * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
+ * the device group accoridng to whether it supports synchronized presets or not.
+ *
+ * @param presetIndex an index of one of the available presets
+ */
+ public void selectPreset(int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ mSelectedPresetIndex = presetIndex;
+ boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
+ mActiveHearingDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice());
+ if (supportSynchronizedPresets) {
+ if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ selectPresetSynchronously(hapGroupId, presetIndex);
+ } else {
+ Log.w(TAG, "supportSynchronizedPresets but hapGroupId is invalid.");
+ selectPresetIndependently(presetIndex);
+ }
+ } else {
+ selectPresetIndependently(presetIndex);
+ }
+ }
+
+ /**
+ * Gets all preset info for {@code mActiveHearingDevice} device.
+ *
+ * @return a list of all known preset info
+ */
+ public List<BluetoothHapPresetInfo> getAllPresetInfo() {
+ if (mActiveHearingDevice == null) {
+ return emptyList();
+ }
+ return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice());
+ }
+
+ /**
+ * Gets the currently active preset for {@code mActiveHearingDevice} device.
+ *
+ * @return active preset index
+ */
+ public int getActivePresetIndex() {
+ if (mActiveHearingDevice == null) {
+ return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ }
+ return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
+ }
+
+ private void selectPresetSynchronously(int groupId, int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "selectPresetSynchronously"
+ + ", presetIndex: " + presetIndex
+ + ", groupId: " + groupId
+ + ", device: " + mActiveHearingDevice.getAddress());
+ }
+ mHapClientProfile.selectPresetForGroup(groupId, presetIndex);
+ }
+
+ private void selectPresetIndependently(int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "selectPresetIndependently"
+ + ", presetIndex: " + presetIndex
+ + ", device: " + mActiveHearingDevice.getAddress());
+ }
+ mHapClientProfile.selectPreset(mActiveHearingDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mActiveHearingDevice.getSubDevice();
+ if (subDevice != null) {
+ if (DEBUG) {
+ Log.d(TAG, "selectPreset for subDevice, device: " + subDevice);
+ }
+ mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
+ }
+ for (final CachedBluetoothDevice memberDevice :
+ mActiveHearingDevice.getMemberDevice()) {
+ if (DEBUG) {
+ Log.d(TAG, "selectPreset for memberDevice, device: " + memberDevice);
+ }
+ mHapClientProfile.selectPreset(memberDevice.getDevice(), presetIndex);
+ }
+ }
+
+ /**
+ * Interface to provide callbacks when preset command result from {@link HapClientProfile}
+ * changed.
+ */
+ public interface PresetCallback {
+ /**
+ * Called when preset info from {@link HapClientProfile} operation get updated.
+ *
+ * @param presetInfos all preset info for {@code mActiveHearingDevice} device
+ * @param activePresetIndex currently active preset index for {@code mActiveHearingDevice}
+ * device
+ */
+ void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex);
+
+ /**
+ * Called when preset operation from {@link HapClientProfile} failed to handle.
+ *
+ * @param reason failure reason
+ */
+ void onPresetCommandFailed(int reason);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
index df27cbb..027f674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
@@ -35,6 +35,8 @@
static final String ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG =
"fingerprint_action_show_reenroll_dialog";
+ static final String EXTRA_IS_REENROLL_FORCED = "is_reenroll_forced";
+
private static final String TAG = "BiometricNotificationBroadcastReceiver";
private final Context mContext;
@@ -56,14 +58,16 @@
mNotificationDialogFactory.createReenrollDialog(
mContext.getUserId(),
mContext::startActivity,
- BiometricSourceType.FACE)
+ BiometricSourceType.FACE,
+ false)
.show();
break;
case ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG:
mNotificationDialogFactory.createReenrollDialog(
mContext.getUserId(),
mContext::startActivity,
- BiometricSourceType.FINGERPRINT)
+ BiometricSourceType.FINGERPRINT,
+ intent.getBooleanExtra(EXTRA_IS_REENROLL_FORCED, false))
.show();
break;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
index fd0feef..4ac5a12 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
@@ -29,13 +29,12 @@
import android.provider.Settings;
import android.util.Log;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import javax.inject.Inject;
-import javax.inject.Provider;
/**
* Manages the creation of dialogs to be shown for biometric re enroll notifications.
@@ -61,7 +60,8 @@
}
Dialog createReenrollDialog(
- int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType) {
+ int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType,
+ boolean isReenrollForced) {
SystemUIDialog sysuiDialog = mSystemUIDialogFactory.create();
if (biometricSourceType == BiometricSourceType.FACE) {
sysuiDialog.setTitle(mResources.getString(R.string.face_re_enroll_dialog_title));
@@ -80,8 +80,12 @@
sysuiDialog.setPositiveButton(R.string.biometric_re_enroll_dialog_confirm,
(dialog, which) -> onReenrollDialogConfirm(
userId, biometricSourceType, activityStarter));
- sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
- (dialog, which) -> {});
+ if (!isReenrollForced) {
+ sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
+ (dialog, which) -> {
+ });
+ }
+ sysuiDialog.setCanceledOnTouchOutside(!isReenrollForced);
return sysuiDialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index d6a4cbb..3b49ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
@@ -43,8 +44,8 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.Optional;
@@ -80,6 +81,8 @@
private boolean mFingerprintNotificationQueued;
private boolean mFingerprintReenrollRequired;
+ private boolean mIsFingerprintReenrollForced;
+
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
private boolean mIsShowing = true;
@@ -118,9 +121,11 @@
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT
- && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ && mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
msgId)) {
mFingerprintReenrollRequired = true;
+ mIsFingerprintReenrollForced =
+ mFingerprintReEnrollNotification.isFingerprintReEnrollForced(msgId);
}
}
};
@@ -191,7 +196,7 @@
final String name = mContext.getString(R.string.face_re_enroll_notification_name);
mHandler.postDelayed(
() -> showNotification(ACTION_SHOW_FACE_REENROLL_DIALOG, title, content, name,
- FACE_NOTIFICATION_ID),
+ FACE_NOTIFICATION_ID, false),
SHOW_NOTIFICATION_DELAY_MS);
}
@@ -204,12 +209,12 @@
final String name = mContext.getString(R.string.fingerprint_re_enroll_notification_name);
mHandler.postDelayed(
() -> showNotification(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG, title, content,
- name, FINGERPRINT_NOTIFICATION_ID),
+ name, FINGERPRINT_NOTIFICATION_ID, mIsFingerprintReenrollForced),
SHOW_NOTIFICATION_DELAY_MS);
}
private void showNotification(String action, CharSequence title, CharSequence content,
- CharSequence name, int notificationId) {
+ CharSequence name, int notificationId, boolean isReenrollForced) {
if (notificationId == FACE_NOTIFICATION_ID) {
mFaceNotificationQueued = false;
} else if (notificationId == FINGERPRINT_NOTIFICATION_ID) {
@@ -223,8 +228,12 @@
}
final Intent onClickIntent = new Intent(action);
+ onClickIntent.putExtra(BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED,
+ isReenrollForced);
+
final PendingIntent onClickPendingIntent = PendingIntent.getBroadcastAsUser(mContext,
- 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE, UserHandle.CURRENT);
+ 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT,
+ UserHandle.CURRENT);
final Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
.setCategory(Notification.CATEGORY_SYSTEM)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
index 9050f26..5b9ed483 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
@@ -23,7 +23,16 @@
*/
public interface FingerprintReEnrollNotification {
//TODO: Remove this class and add a constant in the HAL API instead (b/281841852)
- /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */
- boolean isFingerprintReEnrollRequired(
+ /**
+ * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL or
+ * FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+ */
+ boolean isFingerprintReEnrollRequested(
+ @BiometricFingerprintConstants.FingerprintAcquired int msgId);
+
+ /**
+ * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+ */
+ boolean isFingerprintReEnrollForced(
@BiometricFingerprintConstants.FingerprintAcquired int msgId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
index 1f86bc6..d47e1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
@@ -23,7 +23,13 @@
*/
public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{
@Override
- public boolean isFingerprintReEnrollRequired(int msgId) {
- return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL;
+ public boolean isFingerprintReEnrollRequested(int msgId) {
+ return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL
+ || msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
+ }
+
+ @Override
+ public boolean isFingerprintReEnrollForced(int msgId) {
+ return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index 2797b7b..07e30ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.keyguard.logging.DeviceEntryIconLogger
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -24,6 +25,8 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
/**
* View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
@@ -37,16 +40,30 @@
deviceEntryIconViewModel: DeviceEntryIconViewModel,
alternateBouncerInteractor: AlternateBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
+ logger: DeviceEntryIconLogger,
) : UdfpsTouchOverlayViewModel {
+ private val deviceEntryViewAlphaIsMostlyVisible: Flow<Boolean> =
+ deviceEntryIconViewModel.deviceEntryViewAlpha
+ .map { it > ALLOW_TOUCH_ALPHA_THRESHOLD }
+ .distinctUntilChanged()
override val shouldHandleTouches: Flow<Boolean> =
combine(
- deviceEntryIconViewModel.deviceEntryViewAlpha,
- alternateBouncerInteractor.isVisible,
- systemUIDialogManager.hideAffordancesRequest
- ) { deviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
- (deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !hideAffordancesRequest) ||
- alternateBouncerVisible
- }
+ deviceEntryViewAlphaIsMostlyVisible,
+ alternateBouncerInteractor.isVisible,
+ systemUIDialogManager.hideAffordancesRequest,
+ ) { canTouchDeviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
+ val shouldHandleTouches =
+ (canTouchDeviceEntryViewAlpha && !hideAffordancesRequest) ||
+ alternateBouncerVisible
+ logger.logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+ shouldHandleTouches,
+ canTouchDeviceEntryViewAlpha,
+ alternateBouncerVisible,
+ hideAffordancesRequest
+ )
+ shouldHandleTouches
+ }
+ .distinctUntilChanged()
companion object {
// only allow touches if the view is still mostly visible
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 179fa87..eaca276 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,6 +1,9 @@
package com.android.systemui.bouncer.ui.binder
import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -30,7 +33,24 @@
) {
view.addView(
ComposeView(view.context).apply {
- setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
+ }
+ )
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 72dcb26..27af99e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -24,6 +24,8 @@
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.CommunalColorsImpl
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
@@ -60,6 +62,8 @@
@Communal
fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
+
companion object {
@Provides
@Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b3002cd..3f92223 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -63,7 +63,7 @@
override val isEditMode = true
- // Only widgets are editable. The CTA tile comes last in the list and remains visible.
+ // Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bdf4e72..1bee83b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.ui.viewmodel
+import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
@@ -38,6 +41,7 @@
class CommunalTransitionViewModel
@Inject
constructor(
+ communalColors: CommunalColors,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
@@ -68,4 +72,13 @@
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
+
+ val recentsBackgroundColor: Flow<Color?> =
+ combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
+ if (showByDefault) {
+ backgroundColor
+ } else {
+ null
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
new file mode 100644
index 0000000..1e04fe7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.content.Context
+import android.graphics.Color
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Wrapper around colors used for the communal UI. */
+interface CommunalColors {
+ /** The background color of the glanceable hub. */
+ val backgroundColor: StateFlow<Color>
+}
+
+@SysUISingleton
+class CommunalColorsImpl
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+) : CommunalColors {
+ override val backgroundColor: StateFlow<Color> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { loadBackgroundColor() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = loadBackgroundColor()
+ )
+
+ private fun loadBackgroundColor(): Color =
+ Color.valueOf(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorOutlineVariant
+ )
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 1003050..00a8259 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -30,6 +30,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
@@ -99,6 +100,7 @@
BatterySaverModule.class,
CollapsedStatusBarFragmentStartableModule.class,
ConnectingDisplayViewModel.StartableModule.class,
+ DefaultBlueprintModule.class,
GestureModule.class,
HeadsUpModule.class,
KeyboardShortcutsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 21ee5bd..23fc8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.policy.BatteryControllerStartable
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -343,4 +344,10 @@
@IntoMap
@ClassKey(HomeControlsDreamStartable::class)
abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
+
+ /** Binds {@link BatteryControllerStartable} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(BatteryControllerStartable::class)
+ abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db6b8fe..04f1ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -255,9 +255,6 @@
val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
releasedFlag("filter_provisioning_network_subscriptions")
- // TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
-
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
releasedFlag("incompatible_charging_battery_icon")
@@ -367,16 +364,6 @@
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
- // TODO(b/254512728): Tracking Bug
- @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
-
-
- // TODO(b/270987164): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
-
- // TODO(b/273800936): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
@@ -487,12 +474,6 @@
@JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
- // 2900 - CentralSurfaces-related flags
-
- // TODO(b/285174336): Tracking Bug
- @JvmField
- val USE_REPOS_FOR_BOUNCER_SHOWING = releasedFlag("use_repos_for_bouncer_showing")
-
/** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
@JvmField
val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4327d18..bccc3c5 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,9 +17,7 @@
package com.android.systemui.haptics.qs
import android.animation.ValueAnimator
-import android.annotation.SuppressLint
import android.os.VibrationEffect
-import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
@@ -27,18 +25,14 @@
import androidx.core.animation.doOnCancel
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/**
* A class that handles the long press visuo-haptic effect for a QS tile.
@@ -55,34 +49,32 @@
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
- val keyguardInteractor: KeyguardInteractor,
- @Background bgScope: CoroutineScope,
-) : View.OnTouchListener {
+ keyguardInteractor: KeyguardInteractor,
+) {
private var effectDuration = 0
/** Current state */
private var _state = MutableStateFlow(State.IDLE)
- val state = _state.stateIn(bgScope, SharingStarted.Lazily, State.IDLE)
+ val state = _state.asStateFlow()
/** Flows for view control and action */
private val _effectProgress = MutableStateFlow<Float?>(null)
- val effectProgress = _effectProgress.stateIn(bgScope, SharingStarted.Lazily, null)
+ val effectProgress = _effectProgress.asStateFlow()
// Actions to perform
private val _postedActionType = MutableStateFlow<ActionType?>(null)
- val actionType: StateFlow<ActionType?> =
+ val actionType: Flow<ActionType?> =
combine(
- _postedActionType,
- keyguardInteractor.isKeyguardDismissible,
- ) { action, isDismissible ->
- if (!isDismissible && action == ActionType.LONG_PRESS) {
- ActionType.RESET_AND_LONG_PRESS
- } else {
- action
- }
+ _postedActionType,
+ keyguardInteractor.isKeyguardDismissible,
+ ) { action, isDismissible ->
+ if (!isDismissible && action == ActionType.LONG_PRESS) {
+ ActionType.RESET_AND_LONG_PRESS
+ } else {
+ action
}
- .stateIn(bgScope, SharingStarted.Lazily, null)
+ }
// Should a tap timeout countdown begin
val shouldWaitForTapTimeout: Flow<Boolean> = state.map { it == State.TIMEOUT_WAIT }
@@ -129,23 +121,7 @@
}
}
- /**
- * Handle relevant touch events for the operation of a Tile.
- *
- * A click action is performed following the relevant logic that originates from the
- * [MotionEvent.ACTION_UP] event depending on the current state.
- */
- @SuppressLint("ClickableViewAccessibility")
- override fun onTouch(view: View?, event: MotionEvent?): Boolean {
- when (event?.actionMasked) {
- MotionEvent.ACTION_DOWN -> handleActionDown()
- MotionEvent.ACTION_UP -> handleActionUp()
- MotionEvent.ACTION_CANCEL -> handleActionCancel()
- }
- return true
- }
-
- private fun handleActionDown() {
+ fun handleActionDown() {
when (_state.value) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
@@ -155,7 +131,7 @@
}
}
- private fun handleActionUp() {
+ fun handleActionUp() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
_postedActionType.value = ActionType.CLICK
@@ -169,7 +145,7 @@
}
}
- private fun handleActionCancel() {
+ fun handleActionCancel() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
setState(State.IDLE)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index ddb9f35..2ef901d 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.haptics.qs
+import android.annotation.SuppressLint
+import android.view.MotionEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
@@ -25,10 +27,9 @@
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
-// TODO(b/332903800)
object QSLongPressEffectViewBinder {
+
fun bind(
tile: QSTileViewImpl,
qsLongPressEffect: QSLongPressEffect?,
@@ -36,11 +37,13 @@
): DisposableHandle? {
if (qsLongPressEffect == null) return null
+ // Set the touch listener as the long-press effect
+ setTouchListener(tile, qsLongPressEffect)
+
return tile.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
// Progress of the effect
- launch("$tag#progress") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#progress" }) {
qsLongPressEffect.effectProgress.collect { progress ->
progress?.let {
if (it == 0f) {
@@ -53,7 +56,7 @@
}
// Action to perform
- launch("$tag#action") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
qsLongPressEffect.actionType.collect { action ->
action?.let {
when (it) {
@@ -70,7 +73,7 @@
}
// Tap timeout wait
- launch("$tag#timeout") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#timeout" }) {
qsLongPressEffect.shouldWaitForTapTimeout
.filter { it }
.collect {
@@ -85,4 +88,16 @@
}
}
}
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
+ tile.setOnTouchListener { _, event ->
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> longPressEffect?.handleActionDown()
+ MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
+ MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
+ }
+ true
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
index b84b01e..f3414b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.log.dagger
-package com.android.systemui.statusbar.pipeline.dagger
-
+import java.lang.annotation.Documented
import javax.inject.Qualifier
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+/** A [com.android.systemui.log.LogBuffer] for DeviceEntryIcon state. */
+@Qualifier @Documented @Retention(AnnotationRetention.RUNTIME) annotation class DeviceEntryIconLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f2013be..5babc8b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -654,4 +654,13 @@
public static LogBuffer provideNavbarOrientationTrackingLogBuffer(LogBufferFactory factory) {
return factory.create("NavbarOrientationTrackingLog", 50);
}
+
+ /** Provides a {@link LogBuffer} for use by the DeviceEntryIcon and related classes. */
+ @Provides
+ @SysUISingleton
+ @DeviceEntryIconLog
+ public static LogBuffer provideDeviceEntryIconLogBuffer(LogBufferFactory factory) {
+ return factory.create("DeviceEntryIconLog", 100);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 14a9179..8dd3379 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -16,12 +16,585 @@
package com.android.systemui.media.controls.ui.binder
+import android.content.Context
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.TransitionDrawable
+import android.os.Trace
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
import android.widget.ImageButton
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.widget.AdaptiveIcon
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.animation.AnimationBindHandler
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
+import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
object MediaControlViewBinder {
+ fun bind(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaControlViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ ) {
+ val mediaCard = viewHolder.player
+ mediaCard.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.player.collectLatest { playerViewModel ->
+ playerViewModel?.let {
+ bindMediaCard(
+ viewHolder,
+ viewController,
+ it,
+ falsingManager,
+ backgroundDispatcher,
+ mainDispatcher,
+ mediaFlags
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun bindMediaCard(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ falsingManager: FalsingManager,
+ backgroundDispatcher: CoroutineDispatcher,
+ mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ ) {
+ with(viewHolder) {
+ // AlbumView uses a hardware layer so that clipping of the foreground is handled with
+ // clipping the album art. Otherwise album art shows through at the edges.
+ albumView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ turbulenceNoiseView.setBlendMode(BlendMode.SCREEN)
+ loadingEffectView.setBlendMode(BlendMode.SCREEN)
+ loadingEffectView.visibility = View.INVISIBLE
+
+ player.contentDescription =
+ viewModel.contentDescription.invoke(viewController.isGutsVisible)
+ player.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (!viewController.isGutsVisible) {
+ viewModel.onClicked(Expandable.fromView(player))
+ }
+ }
+ }
+ player.setOnLongClickListener {
+ if (!falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ if (!viewController.isGutsVisible) {
+ openGuts(viewHolder, viewController, viewModel)
+ } else {
+ closeGuts(viewHolder, viewController, viewModel)
+ }
+ }
+ return@setOnLongClickListener true
+ }
+ }
+
+ viewController.bindSeekBar(viewModel.onSeek, viewModel.onBindSeekbar)
+ bindOutputSwitcherModel(
+ viewHolder,
+ viewModel.outputSwitcher,
+ viewController,
+ falsingManager
+ )
+ bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
+ bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
+ bindScrubbingTime(viewHolder, viewModel, viewController)
+
+ val isSongUpdated = bindSongMetadata(viewHolder, viewModel, viewController)
+
+ bindArtworkAndColor(
+ viewHolder,
+ viewModel,
+ viewController,
+ backgroundDispatcher,
+ mainDispatcher,
+ mediaFlags,
+ isSongUpdated
+ )
+
+ // TODO: We don't need to refresh this state constantly, only if the
+ // state actually changed to something which might impact the
+ // measurement. State refresh interferes with the translation
+ // animation, only run it if it's not running.
+ if (!viewController.metadataAnimationHandler.isRunning) {
+ // Don't refresh in scene framework, because it will calculate
+ // with invalid layout sizes
+ if (!mediaFlags.isSceneContainerEnabled()) {
+ viewController.refreshState()
+ }
+ }
+
+ if (viewModel.playTurbulenceNoise) {
+ viewController.setUpTurbulenceNoise()
+ }
+ }
+
+ private fun bindOutputSwitcherModel(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaOutputSwitcherViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ with(viewHolder.seamless) {
+ visibility = View.VISIBLE
+ isEnabled = viewModel.isTapEnabled
+ contentDescription = viewModel.deviceString
+ setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+ viewModel.onClicked.invoke(Expandable.fromView(viewHolder.seamlessButton))
+ }
+ }
+ }
+ when (viewModel.deviceIcon) {
+ is Icon.Loaded -> {
+ val icon = viewModel.deviceIcon.drawable
+ if (icon is AdaptiveIcon) {
+ icon.setBackgroundColor(viewController.colorSchemeTransition.bgColor)
+ }
+ viewHolder.seamlessIcon.setImageDrawable(icon)
+ }
+ is Icon.Resource -> viewHolder.seamlessIcon.setImageResource(viewModel.deviceIcon.res)
+ }
+ viewHolder.seamlessButton.alpha = viewModel.alpha
+ viewHolder.seamlessText.text = viewModel.deviceString
+ }
+
+ private fun bindGutsViewModel(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val gutsViewHolder = viewHolder.gutsViewHolder
+ val model = viewModel.gutsMenu
+ with(gutsViewHolder) {
+ gutsText.text = model.gutsText
+ dismissText.visibility = if (model.isDismissEnabled) View.VISIBLE else View.GONE
+ dismiss.isEnabled = model.isDismissEnabled
+ dismiss.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ model.onDismissClicked.invoke()
+ }
+ }
+ cancelText.background = model.cancelTextBackground
+ cancel.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts(viewHolder, viewController, viewModel)
+ }
+ }
+ settings.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ model.onSettingsClicked.invoke()
+ }
+ }
+ setDismissible(model.isDismissEnabled)
+ setTextPrimaryColor(model.textPrimaryColor)
+ setAccentPrimaryColor(model.accentPrimaryColor)
+ setSurfaceColor(model.surfaceColor)
+ }
+ }
+
+ private fun bindActionButtons(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val genericButtons = MediaViewHolder.genericButtonIds.map { viewHolder.getAction(it) }
+ val expandedSet = viewController.expandedLayout
+ val collapsedSet = viewController.collapsedLayout
+ if (viewModel.useSemanticActions) {
+ // Hide all generic buttons
+ genericButtons.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, false)
+ setVisibleAndAlpha(collapsedSet, it.id, false)
+ }
+
+ SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id ->
+ val button = viewHolder.getAction(id)
+ val actionViewModel = viewModel.actionButtons[index]
+ if (button.id == R.id.actionPrev) {
+ actionViewModel?.let {
+ viewController.setUpPrevButtonInfo(true, it.notVisibleValue)
+ }
+ } else if (button.id == R.id.actionNext) {
+ actionViewModel?.let {
+ viewController.setUpNextButtonInfo(true, it.notVisibleValue)
+ }
+ }
+ actionViewModel?.let { action ->
+ val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler
+ animHandler.tryExecute {
+ if (animHandler.updateRebindId(action.rebindId)) {
+ animHandler.unregisterAll()
+ animHandler.tryRegister(action.icon)
+ animHandler.tryRegister(action.background)
+ bindButtonCommon(
+ button,
+ viewHolder.multiRippleView,
+ action,
+ viewController,
+ falsingManager,
+ )
+ }
+ val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing
+ setSemanticButtonVisibleAndAlpha(
+ viewHolder.getAction(id),
+ viewController.expandedLayout,
+ viewController.collapsedLayout,
+ visible,
+ action.notVisibleValue,
+ action.showInCollapsed
+ )
+ }
+ }
+ ?: clearButton(button)
+ }
+ } else {
+ // Hide buttons that only appear for semantic actions
+ SEMANTIC_ACTIONS_COMPACT.forEach { buttonId ->
+ setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+ setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+ }
+
+ // Set all generic buttons
+ genericButtons.forEachIndexed { index, button ->
+ if (index < viewModel.actionButtons.size) {
+ viewModel.actionButtons[index]?.let { action ->
+ bindButtonCommon(
+ button,
+ viewHolder.multiRippleView,
+ action,
+ viewController,
+ falsingManager,
+ )
+ setVisibleAndAlpha(expandedSet, button.id, visible = true)
+ setVisibleAndAlpha(
+ collapsedSet,
+ button.id,
+ visible = action.showInCollapsed
+ )
+ }
+ ?: clearButton(button)
+ } else {
+ // Hide any unused buttons
+ clearButton(button)
+ setVisibleAndAlpha(expandedSet, button.id, visible = false)
+ setVisibleAndAlpha(collapsedSet, button.id, visible = false)
+ }
+ }
+ }
+ updateSeekBarVisibility(viewController.expandedLayout, isSeekBarEnabled = false)
+ }
+
+ private fun bindButtonCommon(
+ button: ImageButton,
+ multiRippleView: MultiRippleView,
+ actionViewModel: MediaActionViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ button.setImageDrawable(actionViewModel.icon)
+ button.background = actionViewModel.background
+ button.contentDescription = actionViewModel.contentDescription
+ button.isEnabled = actionViewModel.isEnabled
+ if (actionViewModel.isEnabled) {
+ button.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+ actionViewModel.onClicked.invoke(it.id)
+
+ viewController.multiRippleController.play(
+ createTouchRippleAnimation(
+ button,
+ viewController.colorSchemeTransition,
+ multiRippleView
+ )
+ )
+
+ if (actionViewModel.icon is Animatable) {
+ actionViewModel.icon.start()
+ }
+
+ if (actionViewModel.background is Animatable) {
+ actionViewModel.background.start()
+ }
+ }
+ }
+ }
+ }
+
+ private fun bindSongMetadata(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ ): Boolean {
+ val expandedSet = viewController.expandedLayout
+ val collapsedSet = viewController.collapsedLayout
+
+ return viewController.metadataAnimationHandler.setNext(
+ Triple(viewModel.titleName, viewModel.artistName, viewModel.isExplicitVisible),
+ {
+ viewHolder.titleText.text = viewModel.titleName
+ viewHolder.artistText.text = viewModel.artistName
+ setVisibleAndAlpha(
+ expandedSet,
+ R.id.media_explicit_indicator,
+ viewModel.isExplicitVisible
+ )
+ setVisibleAndAlpha(
+ collapsedSet,
+ R.id.media_explicit_indicator,
+ viewModel.isExplicitVisible
+ )
+
+ // refreshState is required here to resize the text views (and prevent ellipsis)
+ viewController.refreshState()
+ },
+ {
+ // After finishing the enter animation, we refresh state. This could pop if
+ // something is incorrectly bound, but needs to be run if other elements were
+ // updated while the enter animation was running
+ viewController.refreshState()
+ }
+ )
+ }
+
+ private suspend fun bindArtworkAndColor(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ backgroundDispatcher: CoroutineDispatcher,
+ mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ updateBackground: Boolean,
+ ) {
+ val traceCookie = viewHolder.hashCode()
+ val traceName = "MediaControlViewBinder#bindArtworkAndColor"
+ Trace.beginAsyncSection(traceName, traceCookie)
+ if (updateBackground) {
+ viewController.isArtworkBound = false
+ }
+ // Capture width & height from views in foreground for artwork scaling in background
+ var width = viewHolder.albumView.measuredWidth
+ var height = viewHolder.albumView.measuredHeight
+ if (mediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+ // TODO(b/312714128): ensure we have a valid size before setting background
+ width = viewController.widthInSceneContainerPx
+ height = viewController.heightInSceneContainerPx
+ }
+ withContext(backgroundDispatcher) {
+ val artwork =
+ if (viewModel.shouldAddGradient) {
+ addGradientToPlayerAlbum(
+ viewHolder.albumView.context,
+ viewModel.backgroundCover!!,
+ viewModel.colorScheme,
+ width,
+ height
+ )
+ } else {
+ ColorDrawable(Color.TRANSPARENT)
+ }
+ withContext(mainDispatcher) {
+ // Transition Colors to current color scheme
+ val colorSchemeChanged =
+ viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme)
+ val albumView = viewHolder.albumView
+ albumView.setPadding(0, 0, 0, 0)
+ if (
+ updateBackground ||
+ colorSchemeChanged ||
+ (!viewController.isArtworkBound && viewModel.shouldAddGradient)
+ ) {
+ viewController.prevArtwork?.let {
+ // Since we throw away the last transition, this will pop if your
+ // backgrounds are cycled too fast (or the correct background arrives very
+ // soon after the metadata changes).
+ val transitionDrawable = TransitionDrawable(arrayOf(it, artwork))
+
+ scaleTransitionDrawableLayer(transitionDrawable, 0, width, height)
+ scaleTransitionDrawableLayer(transitionDrawable, 1, width, height)
+ transitionDrawable.setLayerGravity(0, Gravity.CENTER)
+ transitionDrawable.setLayerGravity(1, Gravity.CENTER)
+ transitionDrawable.isCrossFadeEnabled = true
+
+ albumView.setImageDrawable(transitionDrawable)
+ transitionDrawable.startTransition(
+ if (viewModel.shouldAddGradient) 333 else 80
+ )
+ }
+ }
+ viewController.isArtworkBound = viewModel.shouldAddGradient
+ viewController.prevArtwork = artwork
+
+ if (viewModel.useGrayColorFilter) {
+ // Used for resume players to use launcher icon
+ viewHolder.appIcon.colorFilter = getGrayscaleFilter()
+ when (viewModel.launcherIcon) {
+ is Icon.Loaded ->
+ viewHolder.appIcon.setImageDrawable(viewModel.launcherIcon.drawable)
+ is Icon.Resource ->
+ viewHolder.appIcon.setImageResource(viewModel.launcherIcon.res)
+ }
+ } else {
+ viewHolder.appIcon.setColorFilter(
+ viewController.colorSchemeTransition.accentPrimary.targetColor
+ )
+ viewHolder.appIcon.setImageIcon(viewModel.appIcon)
+ }
+ Trace.endAsyncSection(traceName, traceCookie)
+ }
+ }
+ }
+
+ private fun scaleTransitionDrawableLayer(
+ transitionDrawable: TransitionDrawable,
+ layer: Int,
+ targetWidth: Int,
+ targetHeight: Int
+ ) {
+ val drawable = transitionDrawable.getDrawable(layer) ?: return
+ val width = drawable.intrinsicWidth
+ val height = drawable.intrinsicHeight
+ val scale =
+ MediaDataUtils.getScaleFactor(Pair(width, height), Pair(targetWidth, targetHeight))
+ if (scale == 0f) return
+ transitionDrawable.setLayerSize(layer, (scale * width).toInt(), (scale * height).toInt())
+ }
+
+ private fun addGradientToPlayerAlbum(
+ context: Context,
+ artworkIcon: android.graphics.drawable.Icon,
+ mutableColorScheme: ColorScheme,
+ width: Int,
+ height: Int
+ ): LayerDrawable {
+ val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
+ return MediaArtworkHelper.setUpGradientColorOnDrawable(
+ albumArt,
+ context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
+ mutableColorScheme,
+ MEDIA_PLAYER_SCRIM_START_ALPHA,
+ MEDIA_PLAYER_SCRIM_END_ALPHA
+ )
+ }
+
+ private fun clearButton(button: ImageButton) {
+ button.setImageDrawable(null)
+ button.contentDescription = null
+ button.isEnabled = false
+ button.background = null
+ }
+
+ private fun bindScrubbingTime(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ ) {
+ val expandedSet = viewController.expandedLayout
+ val visible = viewModel.canShowTime && viewController.isScrubbing
+ viewController.canShowScrubbingTime = viewModel.canShowTime
+ setVisibleAndAlpha(expandedSet, viewHolder.scrubbingElapsedTimeView.id, visible)
+ setVisibleAndAlpha(expandedSet, viewHolder.scrubbingTotalTimeView.id, visible)
+ // Collapsed view is always GONE as set in XML, so doesn't need to be updated dynamically.
+ }
+
+ private fun createTouchRippleAnimation(
+ button: ImageButton,
+ colorSchemeTransition: ColorSchemeTransition,
+ multiRippleView: MultiRippleView
+ ): RippleAnimation {
+ val maxSize = (multiRippleView.width * 2).toFloat()
+ return RippleAnimation(
+ RippleAnimationConfig(
+ RippleShader.RippleShape.CIRCLE,
+ duration = 1500L,
+ centerX = button.x + button.width * 0.5f,
+ centerY = button.y + button.height * 0.5f,
+ maxSize,
+ maxSize,
+ button.context.resources.displayMetrics.density,
+ colorSchemeTransition.accentPrimary.currentColor,
+ opacity = 100,
+ sparkleStrength = 0f,
+ baseRingFadeParams = null,
+ sparkleRingFadeParams = null,
+ centerFillFadeParams = null,
+ shouldDistort = false
+ )
+ )
+ }
+
+ private fun openGuts(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ ) {
+ viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+ viewController.openGuts()
+ viewHolder.player.contentDescription = viewModel.contentDescription.invoke(true)
+ viewModel.onLongClicked.invoke()
+ }
+
+ private fun closeGuts(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ ) {
+ viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+ viewController.closeGuts(false)
+ viewHolder.player.contentDescription = viewModel.contentDescription.invoke(false)
+ }
+
fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE)
}
@@ -62,4 +635,10 @@
setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue)
setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed)
}
+
+ private fun getGrayscaleFilter(): ColorMatrixColorFilter {
+ val matrix = ColorMatrix()
+ matrix.setSaturation(0f)
+ return ColorMatrixColorFilter(matrix)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index db4a7fa..b50ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -71,8 +71,6 @@
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -219,10 +217,8 @@
private final Region mExcludeRegion = new Region();
private final Region mDesktopModeExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
// The left side edge width where touch down is allowed
@@ -264,8 +260,6 @@
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mIsNewBackAffordanceEnabled;
- private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
@@ -413,9 +407,7 @@
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -435,13 +427,9 @@
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
- mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
- Flags.TRACKPAD_GESTURE_FEATURES);
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -559,12 +547,10 @@
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- if (mIsTrackpadGestureFeaturesEnabled) {
- mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
- int [] inputDevices = mInputManager.getInputDeviceIds();
- for (int inputDeviceId : inputDevices) {
- mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
- }
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+ int [] inputDevices = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDevices) {
+ mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
}
updateIsEnabled();
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
@@ -616,9 +602,8 @@
try {
Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
- mIsGestureHandlingEnabled =
- mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
- && mIsTrackpadConnected);
+ mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav
+ && mIsTrackpadConnected);
boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
if (isEnabled == mIsEnabled) {
return;
@@ -678,7 +663,6 @@
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
- mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -701,12 +685,7 @@
}
private void resetEdgeBackPlugin() {
- if (mIsNewBackAffordanceEnabled) {
- setEdgeBackPlugin(
- mBackPanelControllerFactory.create(mContext));
- } else {
- setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
- }
+ setEdgeBackPlugin(mBackPanelControllerFactory.create(mContext));
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -1001,8 +980,7 @@
Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
}
- mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
- mIsTrackpadGestureFeaturesEnabled, ev);
+ mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(ev);
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
@@ -1027,7 +1005,7 @@
&& !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
- && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+ && !isTrackpadScroll(ev);
if (mIsTrackpadThreeFingerSwipe) {
// Trackpad back gestures don't have zones, so we don't need to check if the down
// event is within insets.
@@ -1321,10 +1299,8 @@
private final Optional<Pip> mPipOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final FalsingManager mFalsingManager;
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
@Inject
@@ -1344,10 +1320,8 @@
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
@@ -1365,9 +1339,7 @@
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
}
@@ -1391,9 +1363,7 @@
mPipOptional,
mDesktopModeOptional,
mFalsingManager,
- mNavBarEdgePanelProvider,
mBackGestureTfClassifierProviderProvider,
- mFeatureFlags,
mLightBarControllerProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
deleted file mode 100644
index 380846e..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ /dev/null
@@ -1,944 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.gestural;
-
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE;
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import androidx.core.graphics.ColorUtils;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.NavigationEdgeBackPlugin;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.VibratorHelper;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
-
- private static final String TAG = "NavigationBarEdgePanel";
-
- private static final boolean ENABLE_FAILSAFE = true;
-
- private static final long COLOR_ANIMATION_DURATION_MS = 120;
- private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
- private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
- private static final long FAILSAFE_DELAY_MS = 200;
-
- /**
- * The time required since the first vibration effect to automatically trigger a click
- */
- private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
-
- /**
- * The size of the protection of the arrow in px. Only used if this is not background protected
- */
- private static final int PROTECTION_WIDTH_PX = 2;
-
- /**
- * The basic translation in dp where the arrow resides
- */
- private static final int BASE_TRANSLATION_DP = 32;
-
- /**
- * The length of the arrow leg measured from the center to the end
- */
- private static final int ARROW_LENGTH_DP = 18;
-
- /**
- * The angle measured from the xAxis, where the leg is when the arrow rests
- */
- private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
-
- /**
- * The angle that is added per 1000 px speed to the angle of the leg
- */
- private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
-
- /**
- * The maximum angle offset allowed due to speed
- */
- private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
-
- /**
- * The thickness of the arrow. Adjusted to match the home handle (approximately)
- */
- private static final float ARROW_THICKNESS_DP = 2.5f;
-
- /**
- * The amount of rubber banding we do for the vertical translation
- */
- private static final int RUBBER_BAND_AMOUNT = 15;
-
- /**
- * The interpolator used to rubberband
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR
- = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
-
- /**
- * The amount of rubber banding we do for the translation before base translation
- */
- private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
-
- /**
- * The interpolator used to rubberband the appearing of the arrow.
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR
- = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
-
- private final WindowManager mWindowManager;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * The paint the arrow is drawn with
- */
- private final Paint mPaint = new Paint();
- /**
- * The paint the arrow protection is drawn with
- */
- private final Paint mProtectionPaint;
-
- private final float mDensity;
- private final float mBaseTranslation;
- private final float mArrowLength;
- private final float mArrowThickness;
-
- /**
- * The minimum delta needed in movement for the arrow to change direction / stop triggering back
- */
- private final float mMinDeltaForSwitch;
- // The closest to y = 0 that the arrow will be displayed.
- private int mMinArrowPosition;
- // The amount the arrow is shifted to avoid the finger.
- private int mFingerOffset;
-
- private final float mSwipeTriggerThreshold;
- private final float mSwipeProgressThreshold;
- private final Path mArrowPath = new Path();
- private final Point mDisplaySize = new Point();
-
- private final SpringAnimation mAngleAnimation;
- private final SpringAnimation mTranslationAnimation;
- private final SpringAnimation mVerticalTranslationAnimation;
- private final SpringForce mAngleAppearForce;
- private final SpringForce mAngleDisappearForce;
- private final ValueAnimator mArrowColorAnimator;
- private final ValueAnimator mArrowDisappearAnimation;
- private final SpringForce mRegularTranslationSpring;
- private final SpringForce mTriggerBackSpring;
- private final LatencyTracker mLatencyTracker;
-
- private VelocityTracker mVelocityTracker;
- private boolean mIsDark = false;
- private boolean mShowProtection = false;
- private int mProtectionColorLight;
- private int mArrowPaddingEnd;
- private int mArrowColorLight;
- private int mProtectionColorDark;
- private int mArrowColorDark;
- private int mProtectionColor;
- private int mArrowColor;
- private RegionSamplingHelper mRegionSamplingHelper;
- private final Rect mSamplingRect = new Rect();
- private WindowManager.LayoutParams mLayoutParams;
- private int mLeftInset;
- private int mRightInset;
-
- /**
- * True if the panel is currently on the left of the screen
- */
- private boolean mIsLeftPanel;
-
- private float mStartX;
- private float mStartY;
- private float mCurrentAngle;
- /**
- * The current translation of the arrow
- */
- private float mCurrentTranslation;
- /**
- * Where the arrow will be in the resting position.
- */
- private float mDesiredTranslation;
-
- private boolean mDragSlopPassed;
- private boolean mArrowsPointLeft;
- private float mMaxTranslation;
- private boolean mTriggerBack;
- private float mPreviousTouchTranslation;
- private float mTotalTouchDelta;
- private float mVerticalTranslation;
- private float mDesiredVerticalTranslation;
- private float mDesiredAngle;
- private float mAngleOffset;
- private int mArrowStartColor;
- private int mCurrentArrowColor;
- private float mDisappearAmount;
- private long mVibrationTime;
- private int mScreenSize;
- private boolean mTrackingBackArrowLatency = false;
-
- private final Handler mHandler = new Handler();
- private final Runnable mFailsafeRunnable = this::onFailsafe;
-
- private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
- = new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- setVisibility(GONE);
- }
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_ANGLE =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentAngle") {
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentAngle(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentAngle();
- }
- };
-
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentTranslation();
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_VERTICAL_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("verticalTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setVerticalTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getVerticalTranslation();
- }
- };
- private BackCallback mBackCallback;
-
- @Inject
- public NavigationBarEdgePanel(
- Context context,
- LatencyTracker latencyTracker,
- VibratorHelper vibratorHelper,
- @Background Executor backgroundExecutor,
- DisplayTracker displayTracker) {
- super(context);
-
- mWindowManager = context.getSystemService(WindowManager.class);
- mVibratorHelper = vibratorHelper;
-
- mDensity = context.getResources().getDisplayMetrics().density;
-
- mBaseTranslation = dp(BASE_TRANSLATION_DP);
- mArrowLength = dp(ARROW_LENGTH_DP);
- mArrowThickness = dp(ARROW_THICKNESS_DP);
- mMinDeltaForSwitch = dp(32);
-
- mPaint.setStrokeWidth(mArrowThickness);
- mPaint.setStrokeCap(Paint.Cap.ROUND);
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeJoin(Paint.Join.ROUND);
-
- mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS);
- mArrowColorAnimator.addUpdateListener(animation -> {
- int newColor = ColorUtils.blendARGB(
- mArrowStartColor, mArrowColor, animation.getAnimatedFraction());
- setCurrentArrowColor(newColor);
- });
-
- mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
- mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mArrowDisappearAnimation.addUpdateListener(animation -> {
- mDisappearAmount = (float) animation.getAnimatedValue();
- invalidate();
- });
-
- mAngleAnimation =
- new SpringAnimation(this, CURRENT_ANGLE);
- mAngleAppearForce = new SpringForce()
- .setStiffness(500)
- .setDampingRatio(0.5f);
- mAngleDisappearForce = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
- .setFinalPosition(90);
- mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
-
- mTranslationAnimation =
- new SpringAnimation(this, CURRENT_TRANSLATION);
- mRegularTranslationSpring = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTriggerBackSpring = new SpringForce()
- .setStiffness(450)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- mVerticalTranslationAnimation =
- new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
- mVerticalTranslationAnimation.setSpring(
- new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-
- mProtectionPaint = new Paint(mPaint);
- mProtectionPaint.setStrokeWidth(mArrowThickness + PROTECTION_WIDTH_PX);
- loadDimens();
-
- loadColors(context);
- updateArrowDirection();
-
- mSwipeTriggerThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_drag_threshold);
- mSwipeProgressThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-
- setVisibility(GONE);
-
- boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId();
- mRegionSamplingHelper = new RegionSamplingHelper(this,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- setIsDark(!isRegionDark, true /* animate */);
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- return mSamplingRect;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return isPrimaryDisplay;
- }
- }, backgroundExecutor);
- mRegionSamplingHelper.setWindowVisible(true);
- mShowProtection = !isPrimaryDisplay;
- mLatencyTracker = latencyTracker;
- }
-
- @Override
- public void onDestroy() {
- cancelFailsafe();
- mWindowManager.removeView(this);
- mRegionSamplingHelper.stop();
- mRegionSamplingHelper = null;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- private void setIsDark(boolean isDark, boolean animate) {
- mIsDark = isDark;
- updateIsDark(animate);
- }
-
- @Override
- public void setIsLeftPanel(boolean isLeftPanel) {
- mIsLeftPanel = isLeftPanel;
- mLayoutParams.gravity = mIsLeftPanel
- ? (Gravity.LEFT | Gravity.TOP)
- : (Gravity.RIGHT | Gravity.TOP);
- }
-
- @Override
- public void setInsets(int leftInset, int rightInset) {
- mLeftInset = leftInset;
- mRightInset = rightInset;
- }
-
- @Override
- public void setDisplaySize(Point displaySize) {
- mDisplaySize.set(displaySize.x, displaySize.y);
- mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
- }
-
- @Override
- public void setBackCallback(BackCallback callback) {
- mBackCallback = callback;
- }
-
- @Override
- public void setLayoutParams(WindowManager.LayoutParams layoutParams) {
- mLayoutParams = layoutParams;
- mWindowManager.addView(this, mLayoutParams);
- }
-
- /**
- * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
- */
- private void adjustSamplingRectToBoundingBox() {
- float translation = mDesiredTranslation;
- if (!mTriggerBack) {
- // Let's take the resting position and bounds as the sampling rect, since we are not
- // visible right now
- translation = mBaseTranslation;
- if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- translation -= getStaticArrowWidth();
- }
- }
- float left = translation - mArrowThickness / 2.0f;
- left = mIsLeftPanel ? left : mSamplingRect.width() - left;
-
- // Let's calculate the position of the end based on the angle
- float width = getStaticArrowWidth();
- float height = polarToCartY(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength * 2.0f;
- if (!mArrowsPointLeft) {
- left -= width;
- }
-
- float top = (getHeight() * 0.5f) + mDesiredVerticalTranslation - height / 2.0f;
- mSamplingRect.offset((int) left, (int) top);
- mSamplingRect.set(mSamplingRect.left, mSamplingRect.top,
- (int) (mSamplingRect.left + width),
- (int) (mSamplingRect.top + height));
- mRegionSamplingHelper.updateSamplingRect();
- }
-
- @Override
- public void onMotionEvent(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragSlopPassed = false;
- resetOnDown();
- mStartX = event.getX();
- mStartY = event.getY();
- setVisibility(VISIBLE);
- updatePosition(event.getY());
- mRegionSamplingHelper.start(mSamplingRect);
- mWindowManager.updateViewLayout(this, mLayoutParams);
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = true;
- break;
- case MotionEvent.ACTION_MOVE:
- handleMoveEvent(event);
- break;
- case MotionEvent.ACTION_UP:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG,
- "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack);
- }
- if (mTriggerBack) {
- triggerBack();
- } else {
- cancelBack();
- }
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_CANCEL");
- }
- cancelBack();
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateArrowDirection();
- loadDimens();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
- canvas.save();
- canvas.translate(
- mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
- (getHeight() * 0.5f) + mVerticalTranslation);
-
- // Let's calculate the position of the end based on the angle
- float x = (polarToCartX(mCurrentAngle) * mArrowLength);
- float y = (polarToCartY(mCurrentAngle) * mArrowLength);
- Path arrowPath = calculatePath(x,y);
- if (mShowProtection) {
- canvas.drawPath(arrowPath, mProtectionPaint);
- }
-
- canvas.drawPath(arrowPath, mPaint);
- canvas.restore();
- if (mTrackingBackArrowLatency) {
- mLatencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = false;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- mMaxTranslation = getWidth() - mArrowPaddingEnd;
- }
-
- private void loadDimens() {
- Resources res = getResources();
- mArrowPaddingEnd = res.getDimensionPixelSize(R.dimen.navigation_edge_panel_padding);
- mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y);
- mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset);
- }
-
- private void updateArrowDirection() {
- // Both panels arrow point the same way
- mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
- invalidate();
- }
-
- private void loadColors(Context context) {
- final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
- final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
- Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme);
- Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme);
- mArrowColorLight = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
- mArrowColorDark = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
- mProtectionColorDark = mArrowColorLight;
- mProtectionColorLight = mArrowColorDark;
- updateIsDark(false /* animate */);
- }
-
- private void updateIsDark(boolean animate) {
- // TODO: Maybe animate protection as well
- mProtectionColor = mIsDark ? mProtectionColorDark : mProtectionColorLight;
- mProtectionPaint.setColor(mProtectionColor);
- mArrowColor = mIsDark ? mArrowColorDark : mArrowColorLight;
- mArrowColorAnimator.cancel();
- if (!animate) {
- setCurrentArrowColor(mArrowColor);
- } else {
- mArrowStartColor = mCurrentArrowColor;
- mArrowColorAnimator.start();
- }
- }
-
- private void setCurrentArrowColor(int color) {
- mCurrentArrowColor = color;
- mPaint.setColor(color);
- invalidate();
- }
-
- private float getStaticArrowWidth() {
- return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
- }
-
- private float polarToCartX(float angleInDegrees) {
- return (float) Math.cos(Math.toRadians(angleInDegrees));
- }
-
- private float polarToCartY(float angleInDegrees) {
- return (float) Math.sin(Math.toRadians(angleInDegrees));
- }
-
- private Path calculatePath(float x, float y) {
- if (!mArrowsPointLeft) {
- x = -x;
- }
- float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount);
- x = x * extent;
- y = y * extent;
- mArrowPath.reset();
- mArrowPath.moveTo(x, y);
- mArrowPath.lineTo(0, 0);
- mArrowPath.lineTo(x, -y);
- return mArrowPath;
- }
-
- private float getCurrentAngle() {
- return mCurrentAngle;
- }
-
- private float getCurrentTranslation() {
- return mCurrentTranslation;
- }
-
- private void triggerBack() {
- mBackCallback.triggerBack();
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.computeCurrentVelocity(1000);
- // Only do the extra translation if we're not already flinging
- boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
- if (isSlow
- || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
- }
-
- // Let's also snap the angle a bit
- if (mAngleOffset > -4) {
- mAngleOffset = Math.max(-8, mAngleOffset - 8);
- updateAngle(true /* animated */);
- }
-
- // Finally, after the translation, animate back and disappear the arrow
- Runnable translationEnd = () -> {
- // let's snap it back
- mAngleOffset = Math.max(0, mAngleOffset + 8);
- updateAngle(true /* animated */);
-
- mTranslationAnimation.setSpring(mTriggerBackSpring);
- // Translate the arrow back a bit to make for a nice transition
- setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
- animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
- .withEndAction(() -> setVisibility(GONE));
- mArrowDisappearAnimation.start();
- // Schedule failsafe in case alpha end callback is not called
- scheduleFailsafe();
- };
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
- float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- translationEnd.run();
- }
- }
- });
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- translationEnd.run();
- }
- }
-
- private void cancelBack() {
- mBackCallback.cancelBack();
-
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(mSetGoneEndListener);
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- setVisibility(GONE);
- }
- }
-
- private void resetOnDown() {
- animate().cancel();
- mAngleAnimation.cancel();
- mTranslationAnimation.cancel();
- mVerticalTranslationAnimation.cancel();
- mArrowDisappearAnimation.cancel();
- mAngleOffset = 0;
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- // Reset the arrow to the side
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false");
- }
- setTriggerBack(false /* triggerBack */, false /* animated */);
- setDesiredTranslation(0, false /* animated */);
- setCurrentTranslation(0);
- updateAngle(false /* animate */);
- mPreviousTouchTranslation = 0;
- mTotalTouchDelta = 0;
- mVibrationTime = 0;
- setDesiredVerticalTransition(0, false /* animated */);
- cancelFailsafe();
- }
-
- private void handleMoveEvent(MotionEvent event) {
- float x = event.getX();
- float y = event.getY();
- float touchTranslation = MathUtils.abs(x - mStartX);
- float yOffset = y - mStartY;
- float delta = touchTranslation - mPreviousTouchTranslation;
- if (Math.abs(delta) > 0) {
- if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
- mTotalTouchDelta += delta;
- } else {
- mTotalTouchDelta = delta;
- }
- }
- mPreviousTouchTranslation = touchTranslation;
-
- // Apply a haptic on drag slop passed
- if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) {
- mDragSlopPassed = true;
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- mVibrationTime = SystemClock.uptimeMillis();
-
- // Let's show the arrow and animate it in!
- mDisappearAmount = 0.0f;
- setAlpha(1f);
- // And animate it go to back by default!
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=true");
- }
- setTriggerBack(true /* triggerBack */, true /* animated */);
- }
-
- // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
- if (touchTranslation > mBaseTranslation) {
- float diff = touchTranslation - mBaseTranslation;
- float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation));
- progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * (mMaxTranslation - mBaseTranslation);
- touchTranslation = mBaseTranslation + progress;
- } else {
- float diff = mBaseTranslation - touchTranslation;
- float progress = MathUtils.saturate(diff / mBaseTranslation);
- progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
- * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
- touchTranslation = mBaseTranslation - progress;
- }
- // By default we just assume the current direction is kept
- boolean triggerBack = mTriggerBack;
-
- // First lets see if we had continuous motion in one direction for a while
- if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
- triggerBack = mTotalTouchDelta > 0;
- }
-
- // Then, let's see if our velocity tells us to change direction
- mVelocityTracker.computeCurrentVelocity(1000);
- float xVelocity = mVelocityTracker.getXVelocity();
- float yVelocity = mVelocityTracker.getYVelocity();
- float velocity = MathUtils.mag(xVelocity, yVelocity);
- mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
- ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
- if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
- mAngleOffset *= -1;
- }
-
- // Last if the direction in Y is bigger than X * 2 we also abort
- if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
- triggerBack = false;
- }
- if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
- + ", mTotalTouchDelta=" + mTotalTouchDelta
- + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
- + ", yOffset=" + yOffset
- + ", x=" + x
- + ", mStartX=" + mStartX);
- }
- setTriggerBack(triggerBack, true /* animated */);
-
- if (!mTriggerBack) {
- touchTranslation = 0;
- } else if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- touchTranslation -= getStaticArrowWidth();
- }
- setDesiredTranslation(touchTranslation, true /* animated */);
- updateAngle(true /* animated */);
-
- float maxYOffset = getHeight() / 2.0f - mArrowLength;
- float progress = MathUtils.constrain(
- Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT),
- 0, 1);
- float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * maxYOffset * Math.signum(yOffset);
- setDesiredVerticalTransition(verticalTranslation, true /* animated */);
- updateSamplingRect();
- }
-
- private void updatePosition(float touchY) {
- float position = touchY - mFingerOffset;
- position = Math.max(position, mMinArrowPosition);
- position -= mLayoutParams.height / 2.0f;
- mLayoutParams.y = MathUtils.constrain((int) position, 0, mDisplaySize.y);
- updateSamplingRect();
- }
-
- private void updateSamplingRect() {
- int top = mLayoutParams.y;
- int left = mIsLeftPanel ? mLeftInset : mDisplaySize.x - mRightInset - mLayoutParams.width;
- int right = left + mLayoutParams.width;
- int bottom = top + mLayoutParams.height;
- mSamplingRect.set(left, top, right, bottom);
- adjustSamplingRectToBoundingBox();
- }
-
- private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
- if (mDesiredVerticalTranslation != verticalTranslation) {
- mDesiredVerticalTranslation = verticalTranslation;
- if (!animated) {
- setVerticalTranslation(verticalTranslation);
- } else {
- mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
- }
- invalidate();
- }
- }
-
- private void setVerticalTranslation(float verticalTranslation) {
- mVerticalTranslation = verticalTranslation;
- invalidate();
- }
-
- private float getVerticalTranslation() {
- return mVerticalTranslation;
- }
-
- private void setDesiredTranslation(float desiredTranslation, boolean animated) {
- if (mDesiredTranslation != desiredTranslation) {
- mDesiredTranslation = desiredTranslation;
- if (!animated) {
- setCurrentTranslation(desiredTranslation);
- } else {
- mTranslationAnimation.animateToFinalPosition(desiredTranslation);
- }
- }
- }
-
- private void setCurrentTranslation(float currentTranslation) {
- mCurrentTranslation = currentTranslation;
- invalidate();
- }
-
- private void setTriggerBack(boolean triggerBack, boolean animated) {
- if (mTriggerBack != triggerBack) {
- mTriggerBack = triggerBack;
- mAngleAnimation.cancel();
- updateAngle(animated);
- // Whenever the trigger back state changes the existing translation animation should be
- // cancelled
- mTranslationAnimation.cancel();
- mBackCallback.setTriggerBack(mTriggerBack);
- }
- }
-
- private void updateAngle(boolean animated) {
- float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
- if (newAngle != mDesiredAngle) {
- if (!animated) {
- setCurrentAngle(newAngle);
- } else {
- mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
- mAngleAnimation.animateToFinalPosition(newAngle);
- }
- mDesiredAngle = newAngle;
- }
- }
-
- private void setCurrentAngle(float currentAngle) {
- mCurrentAngle = currentAngle;
- invalidate();
- }
-
- private void scheduleFailsafe() {
- if (!ENABLE_FAILSAFE) {
- return;
- }
- cancelFailsafe();
- mHandler.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY_MS);
- }
-
- private void cancelFailsafe() {
- mHandler.removeCallbacks(mFailsafeRunnable);
- }
-
- private void onFailsafe() {
- setVisibility(GONE);
- }
-
- private float dp(float dp) {
- return mDensity * dp;
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println("NavigationBarEdgePanel:");
- pw.println(" mIsLeftPanel=" + mIsLeftPanel);
- pw.println(" mTriggerBack=" + mTriggerBack);
- pw.println(" mDragSlopPassed=" + mDragSlopPassed);
- pw.println(" mCurrentAngle=" + mCurrentAngle);
- pw.println(" mDesiredAngle=" + mDesiredAngle);
- pw.println(" mCurrentTranslation=" + mCurrentTranslation);
- pw.println(" mDesiredTranslation=" + mDesiredTranslation);
- pw.println(" mTranslationAnimation running=" + mTranslationAnimation.isRunning());
- mRegionSamplingHelper.dump(pw);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 10a88c8..b46f2d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -24,16 +24,12 @@
public final class Utilities {
- public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
+ public static boolean isTrackpadScroll(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
}
- public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
&& event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 9698548d..54a59f30 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -38,7 +38,7 @@
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
-internal class NoteTaskInitializer
+class NoteTaskInitializer
@Inject
constructor(
private val controller: NoteTaskController,
@@ -138,11 +138,12 @@
* Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise.
*/
private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? {
- val entryPoint = when {
- keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
- keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
- else -> null
- }
+ val entryPoint =
+ when {
+ keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
+ keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
+ else -> null
+ }
debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" }
return entryPoint
}
@@ -164,7 +165,9 @@
// For now, trigger action immediately on UP of a single press, without waiting for
// the multi-press timeout to expire.
- debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" }
+ debugLog {
+ "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress"
+ }
return !isMultiPress && !isLongPress
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 4ee2db7..042fb63f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -75,6 +75,10 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ // set layer to make alpha animation of brightness slider nicer - otherwise elements
+ // of slider are animated separately and it doesn't look good. See b/329244723
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
mQSPanel = findViewById(R.id.quick_settings_panel);
mHeader = findViewById(R.id.header);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ca71870..40cf4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -185,8 +185,9 @@
private val colorEvaluator = ArgbEvaluator.getInstance()
val isLongPressEffectInitialized: Boolean
get() = longPressEffect?.hasInitialized == true
- @VisibleForTesting
- var longPressEffectHandle: DisposableHandle? = null
+ private var longPressEffectHandle: DisposableHandle? = null
+ val isLongPressEffectBound: Boolean
+ get() = longPressEffectHandle != null
init {
val typedValue = TypedValue()
@@ -621,11 +622,14 @@
// Long-press effects
if (state.handlesLongClick &&
longPressEffect?.initializeEffect(longPressEffectDuration) == true) {
- // set the valid long-press effect as the touch listener
- if (longPressEffectHandle == null) {
+ // bind the long-press effect and set it as the touch listener
+ if (!isLongPressEffectBound) {
longPressEffectHandle =
- QSLongPressEffectViewBinder.bind(this, longPressEffect, state.spec)
- setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(
+ this,
+ longPressEffect,
+ state.spec,
+ )
}
showRippleEffect = false
initializeLongPressProperties()
@@ -634,8 +638,7 @@
// handle a long-press. In this case, we go back to the behaviour of a regular tile
// and clean-up the resources
setOnTouchListener(null)
- longPressEffectHandle?.dispose()
- longPressEffectHandle = null
+ unbindLongPressEffect()
showRippleEffect = isClickable
initialLongPressProperties = null
finalLongPressProperties = null
@@ -827,6 +830,11 @@
changeCornerRadius(newRadius)
}
+ private fun unbindLongPressEffect() {
+ longPressEffectHandle?.dispose()
+ longPressEffectHandle = null
+ }
+
private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
start + fraction * (end - start)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d34a86..4e290e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -47,6 +47,7 @@
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
+import kotlin.jvm.optionals.getOrElse
class IssueRecordingService
@Inject
@@ -140,15 +141,25 @@
}
private fun shareRecording(screenRecording: Uri?) {
- val sharableUri: Uri =
- zipAndPackageRecordings(
- TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- screenRecording
- )
- ?: return
+ val traces =
+ TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+ Log.v(
+ TAG,
+ "Traces were not present. This can happen if users double" +
+ "click on share notification. Traces are cleaned up after sharing" +
+ "so they won't be present for the 2nd share attempt."
+ )
+ return
+ }
+ val perfetto = FileProvider.getUriForFile(this, AUTHORITY, traces.first())
+ val urisToShare = mutableListOf(perfetto)
+ traces.removeFirst()
+
+ getZipWinscopeFileUri(traces)?.let { urisToShare.add(it) }
+ screenRecording?.let { urisToShare.add(it) }
+
val sendIntent =
- FileSender.buildSendIntent(this, listOf(sharableUri))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ FileSender.buildSendIntent(this, urisToShare).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
@@ -161,7 +172,7 @@
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
+ private fun getZipWinscopeFileUri(traceFiles: List<File>): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -171,13 +182,6 @@
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecording != null) {
- contentResolver.openInputStream(screenRecording)?.use {
- os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
- it.transferTo(os)
- os.closeEntry()
- }
- }
}
return FileProvider.getUriForFile(this, AUTHORITY, outZip)
} catch (e: Exception) {
@@ -192,8 +196,7 @@
private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
private const val ZIP_SUFFIX = ".zip"
- private const val TEMP_FILE_PREFIX = "issue_recording"
- private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+ private const val TEMP_FILE_PREFIX = "winscope_recordings"
private val DEFAULT_TRACE_TAGS = listOf<String>()
private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ec7707c..e56a4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -22,7 +22,7 @@
interface TakeScreenshotExecutor {
suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
)
fun onCloseSystemDialogsReceived()
@@ -30,7 +30,7 @@
fun onDestroy()
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
)
}
@@ -65,7 +65,7 @@
*/
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
@@ -86,7 +86,7 @@
/** All logging should be triggered only by this method. */
private suspend fun dispatchToController(
rawScreenshotData: ScreenshotData,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
callback: RequestCallback
) {
// Let's wait before logging "screenshot requested", as we should log the processed
@@ -185,7 +185,7 @@
/** For java compatibility only. see [executeScreenshots] */
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
) {
mainScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index d62ab85..1945c25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -39,11 +39,11 @@
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a private profile app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
// Find the first visible rootTaskInfo with a child task owned by a private user
- val (rootTask, childTask) =
+ val childTask =
content.rootTasks
.filter { it.isVisible }
.firstNotNullOfOrNull { root ->
@@ -52,22 +52,24 @@
.firstOrNull {
profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
}
- ?.let { root to it }
}
- ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")
+ ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
// If matched, return parameters needed to modify the request.
return Matched(
policy = NAME,
- reason = "At least one private profile task is visible",
+ reason = PRIVATE_TASK_VISIBLE,
CaptureParameters(
type = FullScreen(content.displayId),
- component = childTask.componentName ?: rootTask.topActivity,
+ component = content.rootTasks.first { it.isVisible }.topActivity,
owner = UserHandle.of(childTask.userId),
)
)
}
companion object {
const val NAME = "PrivateProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val NO_VISIBLE_TASKS = "No private profile tasks are visible"
+ const val PRIVATE_TASK_VISIBLE = "At least one private profile task is visible"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index 3789371..f768cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -30,3 +30,5 @@
)
}
}
+
+internal fun RootTaskInfo.hasChildTasks() = childTaskUserIds.isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index b781ae9..fdf16aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot.policy
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.os.UserHandle
import com.android.systemui.screenshot.data.model.DisplayContentModel
@@ -24,6 +25,7 @@
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.window.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -41,26 +43,36 @@
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a work app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
+ }
+
+ if (Flags.enableDesktopWindowingMode()) {
+ content.rootTasks.firstOrNull()?.also {
+ if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
+ return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
+ }
+ }
}
// Find the first non PiP rootTask with a top child task owned by a work user
val (rootTask, childTask) =
content.rootTasks
- .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .filter {
+ it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED && it.hasChildTasks()
+ }
.map { it to it.childTasksTopDown().first() }
.firstOrNull { (_, child) ->
profileTypes.getProfileType(child.userId) == ProfileType.WORK
}
?: return NotMatched(
policy = NAME,
- reason = "The top-most non-PINNED task does not belong to a work profile user"
+ reason = WORK_TASK_NOT_TOP,
)
// If matched, return parameters needed to modify the request.
return PolicyResult.Matched(
policy = NAME,
- reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
+ reason = WORK_TASK_IS_TOP,
CaptureParameters(
type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
component = childTask.componentName ?: rootTask.topActivity,
@@ -70,6 +82,13 @@
}
companion object {
- val NAME = "WorkProfile"
+ const val NAME = "WorkProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val WORK_TASK_NOT_TOP =
+ "The top-most non-PINNED task does not belong to a work profile user"
+ const val WORK_TASK_IS_TOP = "The top-most non-PINNED task belongs to a work profile user"
+ const val DESKTOP_MODE_ENABLED =
+ "enable_desktop_windowing_mode is enabled and top " +
+ "RootTask has WINDOWING_MODE_FREEFORM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 92006a4..e051dab 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -63,7 +63,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- setLayerType(LAYER_TYPE_HARDWARE, null);
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index f418e7e..8f6b9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -36,6 +36,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -64,6 +65,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
+ private val communalColors: CommunalColors,
@Communal private val dataSourceDelegator: SceneDataSourceDelegator,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -168,6 +170,7 @@
PlatformTheme {
CommunalContainer(
viewModel = communalViewModel,
+ colors = communalColors,
dataSourceDelegator = dataSourceDelegator,
dialogFactory = dialogFactory,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b8512f2..aa915e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,6 +191,7 @@
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -291,7 +292,6 @@
*/
public final boolean mAnimateBack;
- private final boolean mTrackpadGestureFeaturesEnabled;
/**
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
@@ -438,6 +438,7 @@
private boolean mExpandingFromHeadsUp;
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
+ private boolean mHeadsUpVisible;
private boolean mHeadsUpAnimatingAway;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -605,6 +606,7 @@
private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+ private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
@@ -770,6 +772,7 @@
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
+ HeadsUpNotificationInteractor headsUpNotificationInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -804,6 +807,7 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
+ mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -886,7 +890,6 @@
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
- mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
mMainDispatcher = mainDispatcher;
@@ -1216,6 +1219,11 @@
}
},
mMainDispatcher);
+
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
+ setHeadsUpVisible(), mMainDispatcher);
+ }
}
@VisibleForTesting
@@ -3055,7 +3063,21 @@
mPanelAlphaEndAction = r;
}
+ private Consumer<Boolean> setHeadsUpVisible() {
+ return (Boolean isHeadsUpVisible) -> {
+ mHeadsUpVisible = isHeadsUpVisible;
+
+ if (isHeadsUpVisible) {
+ updateNotificationTranslucency();
+ }
+ updateExpansionAndVisibility();
+ updateGestureExclusionRect();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
+ };
+ }
+
private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
updateVisibility();
@@ -3071,13 +3093,16 @@
}
private boolean shouldPanelBeVisible() {
- boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
return headsUpVisible || isExpanded() || mBouncerShowing;
}
private void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
- mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ }
mHeadsUpTouchHelper = new HeadsUpTouchHelper(
headsUpManager,
mStatusBarService,
@@ -3165,8 +3190,9 @@
}
private boolean isPanelVisibleBecauseOfHeadsUp() {
- return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
- && mBarState == StatusBarState.SHADE;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+ return headsUpVisible && mBarState == StatusBarState.SHADE;
}
private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
@@ -3479,6 +3505,7 @@
ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+ ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4384,6 +4411,8 @@
private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
+
if (inPinnedMode) {
mHeadsUpExistenceChangedRunnable.run();
updateNotificationTranslucency();
@@ -4400,9 +4429,7 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (!isKeyguardShowing()) {
mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
@@ -4411,9 +4438,7 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the
@@ -4898,9 +4923,8 @@
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
boolean canCollapsePanel = canCollapsePanelOnTouch();
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -4920,7 +4944,7 @@
mIsTrackpadReverseScroll =
!mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
- && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
+ && isTrackpadScroll(event);
if (!isTracking() || isFullyCollapsed()) {
mInitialExpandY = y;
mInitialExpandX = x;
@@ -5143,9 +5167,8 @@
mIgnoreXTouchSlop = true;
}
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a763641..907cf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -104,7 +103,6 @@
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -211,7 +209,6 @@
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mGlanceableHubContainerController = glanceableHubContainerController;
- mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -643,16 +640,10 @@
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
- if (mIsTrackpadCommonEnabled) {
- event = MotionEvent.obtain(mDownEvent);
- event.setDownTime(now);
- event.setAction(MotionEvent.ACTION_CANCEL);
- event.setLocation(0.0f, 0.0f);
- } else {
- event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- }
+ event = MotionEvent.obtain(mDownEvent);
+ event.setDownTime(now);
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ event.setLocation(0.0f, 0.0f);
Log.w(TAG, "Canceling current touch event (should be very rare)");
mView.dispatchTouchEvent(event);
event.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index e3db626..222b070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -33,9 +33,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -813,7 +813,7 @@
initialTouchX = x
isTrackpadReverseScroll =
!naturalScrollingSettingObserver.isNaturalScrollingEnabled &&
- isTrackpadScroll(true, event)
+ isTrackpadScroll(event)
}
MotionEvent.ACTION_MOVE -> {
val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index 77660eb..e9306a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.data.repository
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* A repository of currently displayed heads up notifications.
@@ -31,11 +32,13 @@
* True if we are exiting the headsUp pinned mode, and some notifications might still be
* animating out. This is used to keep their view container visible.
*/
- val isHeadsUpAnimatingAway: Flow<Boolean>
+ val isHeadsUpAnimatingAway: StateFlow<Boolean>
/** The heads up row that should be displayed on top. */
val topHeadsUpRow: Flow<HeadsUpRowRepository?>
/** Set of currently active top-level heads up rows to be displayed. */
val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 7f94da3..98b52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
@@ -67,6 +67,9 @@
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ repository.setHeadsUpAnimatingAway(animatingAway)
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 4ebb699..271b0a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -39,13 +39,13 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
/**
* Inflates and updates icons associated with notifications
@@ -239,8 +239,8 @@
val sbi = icon.toStatusBarIcon(entry)
- // Cache if important conversation.
- if (isImportantConversation(entry)) {
+ // Cache if important conversation or app icon.
+ if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) {
if (showPeopleAvatar) {
entry.icons.peopleAvatarDescriptor = sbi
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index bfeaced..2fdd2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -20,6 +20,7 @@
import android.view.View
import com.android.app.tracing.traceSection
import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR
@@ -34,9 +35,12 @@
// view-model (which, at the time of this writing, does not yet exist).
suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
- color.collectTracingEach("SBIV#bindColor") { color ->
- view.staticDrawableColor = color
- view.setDecorColor(color)
+ // Don't change the icon color if an app icon experiment is enabled.
+ if (!android.app.Flags.notificationsUseAppIcon()) {
+ color.collectTracingEach("SBIV#bindColor") { color ->
+ view.staticDrawableColor = color
+ view.setDecorColor(color)
+ }
}
}
@@ -53,12 +57,15 @@
iconColors: Flow<NotificationIconColors>,
contrastColorUtil: ContrastColorUtil,
) {
- iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
- val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
- val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
- view.staticDrawableColor =
- if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
- view.setDecorColor(colors.tint)
+ // Don't change the icon color if an app icon experiment is enabled.
+ if (!android.app.Flags.notificationsUseAppIcon()) {
+ iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
+ val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
+ val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
+ view.staticDrawableColor =
+ if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
+ view.setDecorColor(colors.tint)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0c8518f..bdeaabf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2069,6 +2069,8 @@
// Remove views that don't translate
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
+ // We don't handle focus highlight in this view, it's done in background drawable instead
+ setDefaultFocusHighlightEnabled(false);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index bb65651..8a1a4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -450,7 +451,9 @@
private boolean mIsClipped;
private Rect mRequestedClipBounds;
private boolean mInHeadsUpPinnedMode;
- private boolean mHeadsUpAnimatingAway;
+ @VisibleForTesting
+ boolean mHeadsUpAnimatingAway;
+ private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
private int mStatusBarState;
private int mUpcomingStatusBarState;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
@@ -4084,7 +4087,14 @@
mSwipeHelper.setIsExpanded(isExpanded);
if (changed) {
mWillExpand = false;
- if (!mIsExpanded) {
+ if (mIsExpanded) {
+ // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
+ // waiting for all child animations to finish.
+ // TODO(b/328390331) Do we need to reset this on QS expanded as well?
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
+ } else {
mGroupExpansionManager.collapseGroups();
mExpandHelper.cancelImmediately();
if (!mIsExpansionChanging) {
@@ -4190,6 +4200,9 @@
void onChildAnimationFinished() {
setAnimationRunning(false);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
requestChildrenUpdate();
runAnimationFinishedRunnables();
clearTransient();
@@ -4717,6 +4730,7 @@
}
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -4750,6 +4764,9 @@
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(true);
+ }
}
requestChildrenUpdate();
}
@@ -4939,11 +4956,28 @@
updateClipping();
}
+ /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
+ mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAwayListener != null) {
+ mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);
+ }
+ }
updateClipping();
}
+ /**
+ * Sets a listener to be notified about the heads up disappear animation state changes. If there
+ * are overlapping animations, it will receive updates when the first disappar animation has
+ * started, and when the last has finished.
+ *
+ * @param headsUpAnimatingAwayListener to be notified about disappear animation state changes.
+ */
+ public void setHeadsUpAnimatingAwayListener(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mHeadsUpAnimatingAwayListener = headsUpAnimatingAwayListener;
+ }
@VisibleForTesting
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
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 06479e5..5e719b1 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
@@ -68,8 +68,6 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -210,11 +208,9 @@
@Nullable
private Boolean mHistoryEnabled;
private int mBarState;
- private boolean mIsBouncerShowingFromCentralSurfaces;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlagsClassic mFeatureFlags;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -745,7 +741,6 @@
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
- FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -793,7 +788,6 @@
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
- mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
@@ -1391,14 +1385,6 @@
}
/**
- * Sets whether the bouncer is currently showing. Should only be called from
- * {@link CentralSurfaces}.
- */
- public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
- mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
- }
-
- /**
* Set the visibility of the view, and propagate it to specific children.
*
* @param visible either the view is visible or not.
@@ -1435,7 +1421,7 @@
// For more details, see: b/228790482
&& !mIsInTransitionToAod
// Don't show any notification content if the bouncer is showing. See b/267060171.
- && !isBouncerShowing();
+ && !mPrimaryBouncerInteractor.isBouncerShowing();
mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
@@ -1443,24 +1429,6 @@
}
/**
- * Returns whether the bouncer is currently showing.
- *
- * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
- * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
- * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
- *
- * This timing difference could be load bearing, which is why we have a feature flag protecting
- * where we fetch the value from. This flag is intended to be short-lived.
- */
- private boolean isBouncerShowing() {
- if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
- return mPrimaryBouncerInteractor.isBouncerShowing();
- } else {
- return mIsBouncerShowingFromCentralSurfaces;
- }
- }
-
- /**
* Update the importantForAccessibility of NotificationStackScrollLayout.
* <p>
* We want the NSSL to be unimportant for accessibility when there's no
@@ -1482,6 +1450,7 @@
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 70aa245..3a89630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -299,4 +299,8 @@
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index b3d8430..52cb48b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -21,8 +21,11 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.util.kotlin.sample
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class HeadsUpNotificationViewBinder
@@ -62,9 +65,18 @@
launch {
viewModel.hasPinnedHeadsUpRow.collect { parentView.setInHeadsUpPinnedMode(it) }
}
+ launch {
+ parentView.isHeadsUpAnimatingAway.collect { viewModel.setHeadsUpAnimatingAway(it) }
+ }
}
private fun obtainView(key: HeadsUpRowKey): ExpandableNotificationRow {
return viewModel.elementKeyFor(key) as ExpandableNotificationRow
}
}
+
+private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean>
+ get() = conflatedCallbackFlow {
+ setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
+ awaitClose { setHeadsUpAnimatingAwayListener(null) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 9268d16..6546db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,7 +24,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -37,16 +36,10 @@
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
@Main private val mainExecutor: DelayableExecutor,
- legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
- activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
+ legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
) : ActivityStarter {
- private val activityStarterInternal: ActivityStarterInternal =
- if (SceneContainerFlag.isEnabled) {
- activityStarterInternal.get()
- } else {
- legacyActivityStarter.get()
- }
+ private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
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 cb3c03e..e9aa7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2416,7 +2416,6 @@
mBouncerShowing = bouncerShowing;
mKeyguardBypassController.setBouncerShowing(bouncerShowing);
mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
- mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
setBouncerShowingForStatusBarComponents(bouncerShowing);
mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ddf37d..8ec8d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -168,7 +168,10 @@
updateResources();
}
});
- javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+ this::onShadeOrQsExpanded);
+ }
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -262,6 +265,7 @@
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
@@ -500,7 +504,7 @@
@Override
@NonNull
- public Flow<Boolean> isHeadsUpAnimatingAway() {
+ public StateFlow<Boolean> isHeadsUpAnimatingAway() {
return mHeadsUpAnimatingAway;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 38b3718..3343779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -658,6 +658,7 @@
updateForHeadsUp(true);
}
+ // TODO(b/328579846) bind the StatusBar visibility to heads up events
void updateForHeadsUp(boolean animate) {
boolean showingKeyguardHeadsUp =
isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 87139ac..da5877b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -98,15 +99,21 @@
// we need to keep the panel open artificially, let's wait until the
//animation
// is finished.
- mHeadsUpManager.setHeadsUpAnimatingAway(true);
+ setHeadsAnimatingAway(true);
mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpAnimatingAway(false);
+ setHeadsAnimatingAway(false);
}
mNotificationRemoteInputManager.onPanelCollapsed();
});
}
}
}
+
+ private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e64..6e24412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,8 +19,6 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -52,12 +50,9 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -131,19 +126,15 @@
impl: CollapsedStatusBarViewBinderImpl
): CollapsedStatusBarViewBinder
- @Binds
- @IntoMap
- @ClassKey(WifiRepositoryDagger::class)
- abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
-
companion object {
+
@Provides
@SysUISingleton
- fun provideWifiRepositoryDagger(
+ fun provideRealWifiRepository(
wifiManager: WifiManager?,
disabledWifiRepository: DisabledWifiRepository,
wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
- ): WifiRepositoryDagger {
+ ): RealWifiRepository {
// If we have a null [WifiManager], then the wifi repository should be permanently
// disabled.
return if (wifiManager == null) {
@@ -155,36 +146,6 @@
@Provides
@SysUISingleton
- fun provideWifiRepositoryViaTrackerLibDagger(
- wifiManager: WifiManager?,
- disabledWifiRepository: DisabledWifiRepository,
- wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
- ): WifiRepositoryViaTrackerLibDagger {
- // If we have a null [WifiManager], then the wifi repository should be permanently
- // disabled.
- return if (wifiManager == null) {
- disabledWifiRepository
- } else {
- wifiRepositoryFromTrackerLibFactory.create(wifiManager)
- }
- }
-
- @Provides
- @SysUISingleton
- fun provideRealWifiRepository(
- wifiRepository: WifiRepositoryDagger,
- wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
- flags: FeatureFlags,
- ): RealWifiRepository {
- return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
- wifiRepositoryFromTrackerLib
- } else {
- wifiRepository
- }
- }
-
- @Provides
- @SysUISingleton
@Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
mobileIconsViewModel: MobileIconsViewModel,
@@ -197,16 +158,8 @@
@Provides
@SysUISingleton
@WifiInputLog
- fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("WifiInputLog", 50)
- }
-
- @Provides
- @SysUISingleton
- @WifiTrackerLibInputLog
- fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
- return factory.create("WifiTrackerLibInputLog", 200)
+ fun provideWifiLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("WifiInputLog", 200)
}
@Provides
@@ -218,13 +171,6 @@
@Provides
@SysUISingleton
- @WifiTrackerLibTableLog
- fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("WifiTrackerLibTableLog", 100)
- }
-
- @Provides
- @SysUISingleton
@AirplaneTableLog
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
index 6db6944..9ba802c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
@@ -18,8 +18,8 @@
import javax.inject.Qualifier
-/** Wifi logs for inputs into the wifi pipeline. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiInputLog
+/**
+ * Wifi logs for inputs into
+ * [com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl].
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class WifiInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b22e09e..fc7a672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
@@ -80,8 +79,3 @@
* repository.
*/
interface RealWifiRepository : WifiRepository
-
-/** Used only by Dagger to bind [WifiRepositoryImpl]. */
-interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
-/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
-interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index ca042e2..af6e8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -25,7 +25,6 @@
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index cfdbe4a..b79fb9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,8 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
@@ -34,10 +33,7 @@
* wifi information.
*/
@SysUISingleton
-class DisabledWifiRepository @Inject constructor() :
- WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
- override fun start() {}
-
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
deleted file mode 100644
index 67dd32f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ /dev/null
@@ -1,114 +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.pipeline.wifi.data.repository.prod
-
-import android.annotation.SuppressLint
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Object to provide shared helper functions between [WifiRepositoryImpl] and
- * [WifiRepositoryViaTrackerLib].
- */
-object WifiRepositoryHelper {
- /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
- fun createActivityFlow(
- wifiManager: WifiManager,
- @Main mainExecutor: Executor,
- scope: CoroutineScope,
- tableLogBuffer: TableLogBuffer,
- inputLogger: (String) -> Unit,
- ): StateFlow<DataActivityModel> {
- return conflatedCallbackFlow {
- val callback =
- WifiManager.TrafficStateCallback { state ->
- inputLogger.invoke(prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
- }
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT,
- )
- }
-
- /**
- * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
- */
- fun createNetworkScanFlow(
- wifiManager: WifiManager,
- scope: CoroutineScope,
- @Background dispatcher: CoroutineDispatcher,
- inputLogger: () -> Unit,
- ): StateFlow<List<WifiScanEntry>> {
- return conflatedCallbackFlow {
- val callback =
- object : WifiManager.ScanResultsCallback() {
- @SuppressLint("MissingPermission")
- override fun onScanResultsAvailable() {
- inputLogger.invoke()
- trySend(wifiManager.scanResults.toModel())
- }
- }
-
- wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
-
- awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
- }
- .stateIn(scope, SharingStarted.Eagerly, emptyList())
- }
-
- private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
-
- // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
- private fun prettyPrintActivity(activity: Int): String {
- return when (activity) {
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
- else -> "INVALID"
- }
- }
-
- private const val ACTIVITY_PREFIX = "wifiActivity"
- val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 59ef884..20e44e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -17,309 +17,447 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.annotation.SuppressLint
-import android.content.IntentFilter
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkRequest
-import android.net.wifi.WifiInfo
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.systemui.broadcast.BroadcastDispatcher
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-/** Real implementation of [WifiRepository]. */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+/**
+ * A real implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ */
@SysUISingleton
-@SuppressLint("MissingPermission")
class WifiRepositoryImpl
@Inject
constructor(
- broadcastDispatcher: BroadcastDispatcher,
- connectivityManager: ConnectivityManager,
- connectivityRepository: ConnectivityRepository,
- logger: WifiInputLogger,
- @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
- @Main mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
private val wifiManager: WifiManager,
-) : WifiRepositoryDagger {
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
+) : RealWifiRepository, LifecycleOwner {
- override fun start() {
- // There are two possible [WifiRepository] implementations: This class (old) and
- // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
- // class to still be running in the background so that we can collect logs and compare
- // discrepancies. This #start method collects on the flows to ensure that the logs are
- // collected.
- scope.launch { isWifiEnabled.collect {} }
- scope.launch { isWifiDefault.collect {} }
- scope.launch { wifiNetwork.collect {} }
- scope.launch { wifiActivity.collect {} }
+ override val lifecycle =
+ LifecycleRegistry(this).also {
+ mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ }
+
+ private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
+ private var wifiPickerTracker: WifiPickerTracker? = null
+
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val secondaryNetworks =
+ if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+ } else {
+ emptyList()
+ }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+ // but with updated internals. For example, when its validation status
+ // changes from false to true, the same instance is re-used but with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the value
+ // (even though the internals have changed). So, we need to transform it
+ // into our internal model immediately. [toWifiNetworkModel] always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+
+ wifiPickerTracker =
+ wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
+ // By default, [WifiPickerTracker] will scan to see all available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and in fact,
+ // running scans is costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
+ // The lifecycle must be STARTED in order for the callback to receive events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
- private val wifiStateChangeEvents: Flow<Unit> =
- broadcastDispatcher
- .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
- .onEach { logger.logIntent("WIFI_STATE_CHANGED_ACTION") }
-
- private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
- // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
- // have changed.
override val isWifiEnabled: StateFlow<Boolean> =
- merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .onStart { emit(Unit) }
- .mapLatest { isWifiEnabled() }
+ wifiPickerTrackerInfo
+ .map { it.state == WifiManager.WIFI_STATE_ENABLED }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
columnPrefix = "",
columnName = COL_NAME_IS_ENABLED,
initialValue = false,
)
- .stateIn(
- scope = scope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ .stateIn(scope, SharingStarted.Eagerly, false)
- // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
- private suspend fun isWifiEnabled(): Boolean =
- withContext(bgDispatcher) { wifiManager.isWifiEnabled }
-
- override val isWifiDefault: StateFlow<Boolean> =
- connectivityRepository.defaultConnections
- // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
- .map { it.wifi.isDefault || it.carrierMerged.isDefault }
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ wifiPickerTrackerInfo
+ .map { it.primaryNetwork }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
+ columnPrefix = "",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ wifiPickerTrackerInfo
+ .map { it.secondaryNetworks }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "",
+ columnName = "secondaryNetworks",
+ initialValue = emptyList(),
+ )
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ /**
+ * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+ * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+ * if it exists, falling back on the connected entry if null
+ */
+ private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+ get() {
+ val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+ return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+ mergedEntry
+ } else {
+ this?.connectedWifiEntry
+ }
+ }
+
+ /**
+ * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+ * primary network. Returns an inactive network if it's not primary.
+ */
+ private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+ return if (!this.isPrimaryNetwork) {
+ WIFI_NETWORK_DEFAULT
+ } else {
+ this.toWifiNetworkModel()
+ }
+ }
+
+ /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+ return if (this is MergedCarrierEntry) {
+ this.convertCarrierMergedToModel()
+ } else {
+ this.convertNormalToModel()
+ }
+ }
+
+ private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+ return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+ } else {
+ WifiNetworkModel.CarrierMerged(
+ networkId = NETWORK_ID,
+ subscriptionId = this.subscriptionId,
+ level = this.level,
+ // WifiManager APIs to calculate the signal level start from 0, so
+ // maxSignalLevel + 1 represents the total level buckets count.
+ numberOfLevels = wifiManager.maxSignalLevel + 1,
+ )
+ }
+ }
+
+ private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+ if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+ // If our level means the network is unreachable or the level is otherwise invalid, we
+ // don't have an active network.
+ return WifiNetworkModel.Inactive
+ }
+
+ val hotspotDeviceType =
+ if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+ this.deviceType.toHotspotDeviceType()
+ } else {
+ WifiNetworkModel.HotspotDeviceType.NONE
+ }
+
+ return WifiNetworkModel.Active(
+ networkId = NETWORK_ID,
+ isValidated = this.hasInternetAccess(),
+ level = this.level,
+ ssid = this.title,
+ hotspotDeviceType = hotspotDeviceType,
+ // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
+ // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+ // always be false/null in this repository.
+ // TODO(b/292534484): Remove these fields from the wifi network model once this
+ // repository is fully enabled.
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+ }
+
+ override val isWifiDefault: StateFlow<Boolean> =
+ wifiPickerTrackerInfo
+ .map { it.isDefault }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
columnPrefix = "",
columnName = COL_NAME_IS_DEFAULT,
initialValue = false,
)
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .stateIn(scope, SharingStarted.Eagerly, false)
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ override val wifiActivity: StateFlow<DataActivityModel> =
conflatedCallbackFlow {
- var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
-
val callback =
- object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- logger.logOnCapabilitiesChanged(
- network,
- networkCapabilities,
- isDefaultNetworkCallback = false,
- )
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifiInfo =
- networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
- if (wifiInfo?.isPrimary == true) {
- val wifiNetworkModel =
- createWifiNetworkModel(
- wifiInfo,
- network,
- networkCapabilities,
- wifiManager,
- )
- currentWifi = wifiNetworkModel
- trySend(wifiNetworkModel)
- }
- }
-
- override fun onLost(network: Network) {
- logger.logOnLost(network, isDefaultNetworkCallback = false)
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifi = currentWifi
- if (
- (wifi is WifiNetworkModel.Active &&
- wifi.networkId == network.getNetId()) ||
- (wifi is WifiNetworkModel.CarrierMerged &&
- wifi.networkId == network.getNetId())
- ) {
- val newNetworkModel = WifiNetworkModel.Inactive
- currentWifi = newNetworkModel
- trySend(newNetworkModel)
- }
- }
+ WifiManager.TrafficStateCallback { state ->
+ logActivity(state)
+ trySend(state.toWifiDataActivityModel())
}
-
- connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
-
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
- .distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
+ tableLogger,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
)
- // There will be multiple wifi icons in different places that will frequently
- // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
- // that new subscribes will get the latest value immediately upon subscription.
- // Otherwise, the views could show stale data. See b/244173280.
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
- initialValue = WIFI_NETWORK_DEFAULT,
+ initialValue = ACTIVITY_DEFAULT,
)
- // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
-
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTableLogBuffer,
- logger::logActivity,
- )
-
override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- logger::logScanResults
- )
+ conflatedCallbackFlow {
+ val callback =
+ object : WifiManager.ScanResultsCallback() {
+ @SuppressLint("MissingPermission")
+ override fun onScanResultsAvailable() {
+ logScanResults()
+ trySend(wifiManager.scanResults.toModel())
+ }
+ }
- companion object {
- // Start out with no known wifi network.
- // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
- // initial fetch to get a starting wifi network. But, it uses a deprecated API
- // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
- // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
- // NetworkCallback inside [wifiNetwork] for our wifi network information.
- val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+ wifiManager.registerScanResultsCallback(bgDispatcher.asExecutor(), callback)
- const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
-
- private fun createWifiNetworkModel(
- wifiInfo: WifiInfo,
- network: Network,
- networkCapabilities: NetworkCapabilities,
- wifiManager: WifiManager,
- ): WifiNetworkModel {
- return if (wifiInfo.isCarrierMerged) {
- if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = network.getNetId(),
- subscriptionId = wifiInfo.subscriptionId,
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- // The WiFi signal level returned by WifiManager#calculateSignalLevel start
- // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
- // buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- } else {
- WifiNetworkModel.Active(
- network.getNetId(),
- isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- wifiInfo.ssid,
- // This repository doesn't support any hotspot information.
- WifiNetworkModel.HotspotDeviceType.NONE,
- wifiInfo.isPasspointAp,
- wifiInfo.isOsuAp,
- wifiInfo.passpointProviderFriendlyName
- )
+ awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
}
- }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
- private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
- NetworkRequest.Builder()
- .clearCapabilities()
- .addCapability(NET_CAPABILITY_NOT_VPN)
- .addTransportType(TRANSPORT_WIFI)
- .addTransportType(TRANSPORT_CELLULAR)
- .build()
+ private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
+ private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = connectedEntry.toString() },
+ { "onWifiEntriesChanged. ConnectedEntry=$str1" },
+ )
}
+ private fun logOnWifiStateChanged(state: Int?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = state ?: -1 },
+ { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
+ )
+ }
+
+ private fun logActivity(activity: Int) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = prettyPrintActivity(activity) },
+ { "onActivityChanged: $str1" }
+ )
+ }
+
+ // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
+ }
+
+ private fun logScanResults() =
+ inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
+ /**
+ * Data class storing all the information fetched from [WifiPickerTracker].
+ *
+ * Used so that we only register a single callback on [WifiPickerTracker].
+ */
+ data class WifiPickerTrackerInfo(
+ /** The current wifi state. See [WifiManager.getWifiState]. */
+ val state: Int,
+ /** True if wifi is currently the default connection and false otherwise. */
+ val isDefault: Boolean,
+ /** The currently primary wifi network. */
+ val primaryNetwork: WifiNetworkModel,
+ /** The current secondary network(s), if any. Specifically excludes the primary network. */
+ val secondaryNetworks: List<WifiNetworkModel>
+ )
+
@SysUISingleton
class Factory
@Inject
constructor(
- private val broadcastDispatcher: BroadcastDispatcher,
- private val connectivityManager: ConnectivityManager,
- private val connectivityRepository: ConnectivityRepository,
- private val logger: WifiInputLogger,
- @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+ private val featureFlags: FeatureFlags,
+ @Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
- broadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- wifiTableLogBuffer,
+ featureFlags,
+ scope,
mainExecutor,
bgDispatcher,
- scope,
+ wifiPickerTrackerFactory,
wifiManager,
+ inputLogger,
+ tableLogger,
)
}
}
+
+ companion object {
+ // Start out with no known wifi network.
+ @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+
+ private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
+ private const val ACTIVITY_PREFIX = "wifiActivity"
+ val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ private const val TAG = "WifiTrackerLibInputLog"
+
+ /**
+ * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+ * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+ * callbacks within the repository.
+ *
+ * Since this class does not need to manually apply framework callbacks and since the
+ * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+ * repository.
+ *
+ * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+ * to [WifiRepositoryImpl].
+ */
+ private const val NETWORK_ID = -1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
deleted file mode 100644
index 1670dd3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ /dev/null
@@ -1,414 +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.pipeline.wifi.data.repository.prod
-
-import android.net.wifi.WifiManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.wifitrackerlib.HotspotNetworkEntry
-import com.android.wifitrackerlib.MergedCarrierEntry
-import com.android.wifitrackerlib.WifiEntry
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
-import com.android.wifitrackerlib.WifiPickerTracker
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
- * truth for wifi information.
- *
- * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
- */
-@SysUISingleton
-class WifiRepositoryViaTrackerLib
-@Inject
-constructor(
- featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- private val wifiManager: WifiManager,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
-) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
-
- override val lifecycle =
- LifecycleRegistry(this).also {
- mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
- }
-
- private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
-
- private var wifiPickerTracker: WifiPickerTracker? = null
-
- private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
- var current =
- WifiPickerTrackerInfo(
- state = WIFI_STATE_DEFAULT,
- isDefault = false,
- primaryNetwork = WIFI_NETWORK_DEFAULT,
- secondaryNetworks = emptyList(),
- )
- callbackFlow {
- val callback =
- object : WifiPickerTracker.WifiPickerTrackerCallback {
- override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
- logOnWifiEntriesChanged(connectedEntry)
-
- val secondaryNetworks =
- if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
- val activeNetworks =
- wifiPickerTracker?.activeWifiEntries ?: emptyList()
- activeNetworks
- .filter { it != connectedEntry && !it.isPrimaryNetwork }
- .map { it.toWifiNetworkModel() }
- } else {
- emptyList()
- }
-
- // [WifiPickerTracker.connectedWifiEntry] will return the same instance
- // but with updated internals. For example, when its validation status
- // changes from false to true, the same instance is re-used but with the
- // validated field updated.
- //
- // Because it's the same instance, the flow won't re-emit the value
- // (even though the internals have changed). So, we need to transform it
- // into our internal model immediately. [toWifiNetworkModel] always
- // returns a new instance, so the flow is guaranteed to emit.
- send(
- newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
- ?: WIFI_NETWORK_DEFAULT,
- newSecondaryNetworks = secondaryNetworks,
- newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
- )
- }
-
- override fun onWifiStateChanged() {
- val state = wifiPickerTracker?.wifiState
- logOnWifiStateChanged(state)
- send(newState = state ?: WIFI_STATE_DEFAULT)
- }
-
- override fun onNumSavedNetworksChanged() {}
-
- override fun onNumSavedSubscriptionsChanged() {}
-
- private fun send(
- newState: Int = current.state,
- newIsDefault: Boolean = current.isDefault,
- newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
- newSecondaryNetworks: List<WifiNetworkModel> =
- current.secondaryNetworks,
- ) {
- val new =
- WifiPickerTrackerInfo(
- newState,
- newIsDefault,
- newPrimaryNetwork,
- newSecondaryNetworks,
- )
- current = new
- trySend(new)
- }
- }
-
- wifiPickerTracker =
- wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
- // By default, [WifiPickerTracker] will scan to see all available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and in fact,
- // running scans is costly and should be avoided whenever possible).
- this?.disableScanning()
- }
- // The lifecycle must be STARTED in order for the callback to receive events.
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
- awaitClose {
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
- }
- }
- .stateIn(scope, SharingStarted.Eagerly, current)
- }
-
- override val isWifiEnabled: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.state == WifiManager.WIFI_STATE_ENABLED }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_ENABLED,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
- wifiPickerTrackerInfo
- .map { it.primaryNetwork }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
- .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
-
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- wifiPickerTrackerInfo
- .map { it.secondaryNetworks }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = "secondaryNetworks",
- initialValue = emptyList(),
- )
- .stateIn(scope, SharingStarted.Eagerly, emptyList())
-
- /**
- * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
- * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
- * if it exists, falling back on the connected entry if null
- */
- private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
- get() {
- val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
- return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
- mergedEntry
- } else {
- this?.connectedWifiEntry
- }
- }
-
- /**
- * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
- * primary network. Returns an inactive network if it's not primary.
- */
- private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
- return if (!this.isPrimaryNetwork) {
- WIFI_NETWORK_DEFAULT
- } else {
- this.toWifiNetworkModel()
- }
- }
-
- /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
- private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
- return if (this is MergedCarrierEntry) {
- this.convertCarrierMergedToModel()
- } else {
- this.convertNormalToModel()
- }
- }
-
- private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
- return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = NETWORK_ID,
- subscriptionId = this.subscriptionId,
- level = this.level,
- // WifiManager APIs to calculate the signal level start from 0, so
- // maxSignalLevel + 1 represents the total level buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- }
-
- private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
- if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
- // If our level means the network is unreachable or the level is otherwise invalid, we
- // don't have an active network.
- return WifiNetworkModel.Inactive
- }
-
- val hotspotDeviceType =
- if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
- this.deviceType.toHotspotDeviceType()
- } else {
- WifiNetworkModel.HotspotDeviceType.NONE
- }
-
- return WifiNetworkModel.Active(
- networkId = NETWORK_ID,
- isValidated = this.hasInternetAccess(),
- level = this.level,
- ssid = this.title,
- hotspotDeviceType = hotspotDeviceType,
- // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
- // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
- // always be false/null in this repository.
- // TODO(b/292534484): Remove these fields from the wifi network model once this
- // repository is fully enabled.
- isPasspointAccessPoint = false,
- isOnlineSignUpForPasspointAccessPoint = false,
- passpointProviderFriendlyName = null,
- )
- }
-
- override val isWifiDefault: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.isDefault }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_DEFAULT,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTrackerLibTableLogBuffer,
- this::logActivity,
- )
-
- override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- this::logScanResults,
- )
-
- private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = connectedEntry.toString() },
- { "onWifiEntriesChanged. ConnectedEntry=$str1" },
- )
- }
-
- private fun logOnWifiStateChanged(state: Int?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { int1 = state ?: -1 },
- { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
- )
- }
-
- private fun logActivity(activity: String) {
- inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
- }
-
- private fun logScanResults() =
- inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-
- /**
- * Data class storing all the information fetched from [WifiPickerTracker].
- *
- * Used so that we only register a single callback on [WifiPickerTracker].
- */
- data class WifiPickerTrackerInfo(
- /** The current wifi state. See [WifiManager.getWifiState]. */
- val state: Int,
- /** True if wifi is currently the default connection and false otherwise. */
- val isDefault: Boolean,
- /** The currently primary wifi network. */
- val primaryNetwork: WifiNetworkModel,
- /** The current secondary network(s), if any. Specifically excludes the primary network. */
- val secondaryNetworks: List<WifiNetworkModel>
- )
-
- @SysUISingleton
- class Factory
- @Inject
- constructor(
- private val featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
- ) {
- fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
- return WifiRepositoryViaTrackerLib(
- featureFlags,
- scope,
- mainExecutor,
- bgDispatcher,
- wifiPickerTrackerFactory,
- wifiManager,
- inputLogger,
- wifiTrackerLibTableLogBuffer,
- )
- }
- }
-
- companion object {
- private const val TAG = "WifiTrackerLibInputLog"
-
- /**
- * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
- * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
- * callbacks within the repository.
- *
- * Since this class does not need to manually apply framework callbacks and since the
- * network ID is not used beyond the repository, it's safe to use an invalid ID in this
- * repository.
- *
- * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
- * to [WifiRepositoryViaTrackerLib].
- */
- private const val NETWORK_ID = -1
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
deleted file mode 100644
index b76bb51..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ /dev/null
@@ -1,66 +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.pipeline.wifi.shared
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
-import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
-import javax.inject.Inject
-
-/**
- * Logger for all the wifi-related inputs (intents, callbacks, etc.) that the wifi repo receives.
- */
-@SysUISingleton
-class WifiInputLogger
-@Inject
-constructor(
- @WifiInputLog val buffer: LogBuffer,
-) {
- fun logOnCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities,
- isDefaultNetworkCallback: Boolean,
- ) {
- LoggerHelper.logOnCapabilitiesChanged(
- buffer,
- TAG,
- network,
- networkCapabilities,
- isDefaultNetworkCallback,
- )
- }
-
- fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
- LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
- }
-
- fun logIntent(intentName: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = intentName }, { "Intent received: $str1" })
- }
-
- fun logActivity(activity: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
- }
-
- fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-}
-
-private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 0c2abd9..f573530 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -69,7 +69,7 @@
runnable.run()
return
}
- val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+ val fn = "[$label] => AvalancheController.update [${getKey(entry)}]"
if (entry == null) {
log { "Entry is NULL, stop update." }
return;
@@ -78,13 +78,13 @@
debugRunnableLabelMap[runnable] = label
}
if (isShowing(entry)) {
- log { "$fn => [update showing]" }
+ log { "\n$fn => [update showing]" }
runnable.run()
} else if (entry in nextMap) {
- log { "$fn => [update next]" }
+ log { "\n$fn => [update next]" }
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "$fn => [showNow]" }
+ log { "\n$fn => [showNow]" }
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -208,24 +208,24 @@
}
private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
- log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+ log { "SHOW: " + getKey(entry) }
headsUpEntryShowing = entry
runnableList.forEach {
if (it in debugRunnableLabelMap) {
- log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ log { "RUNNABLE: ${debugRunnableLabelMap[it]}" }
}
it.run()
}
}
private fun showNext() {
- log { "showNext" }
+ log { "SHOW NEXT" }
headsUpEntryShowing = null
if (nextList.isEmpty()) {
- log { "no more to show!" }
+ log { "NO MORE TO SHOW" }
return
}
@@ -265,41 +265,44 @@
}
private fun getStateStr(): String {
- return "SHOWING: ${getKey(headsUpEntryShowing)}" +
- "\tNEXT LIST: $nextListStr\tMAP: $nextMapStr" +
- "\tDROP: $dropSetStr"
+ return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
+ "\nNEXT LIST: $nextListStr" +
+ "\nNEXT MAP: $nextMapStr" +
+ "\nDROPPED: $dropSetStr"
}
private fun logState(reason: String) {
- log { "REASON $reason" }
+ log { "\n================================================================================="}
+ log { "STATE $reason" }
log { getStateStr() }
+ log { "=================================================================================\n"}
}
private val dropSetStr: String
get() {
val queue = ArrayList<String>()
for (entry in debugDropSet) {
- queue.add(getKey(entry))
+ queue.add("[${getKey(entry)}]")
}
- return java.lang.String.join(" ", queue)
+ return java.lang.String.join("\n", queue)
}
private val nextListStr: String
get() {
val queue = ArrayList<String>()
for (entry in nextList) {
- queue.add(getKey(entry))
+ queue.add("[${getKey(entry)}]")
}
- return java.lang.String.join(" ", queue)
+ return java.lang.String.join("\n", queue)
}
private val nextMapStr: String
get() {
val queue = ArrayList<String>()
for (entry in nextMap.keys) {
- queue.add(getKey(entry))
+ queue.add("[${getKey(entry)}]")
}
- return java.lang.String.join(" ", queue)
+ return java.lang.String.join("\n", queue)
}
fun getKey(entry: HeadsUpEntry?): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 45078e3..46ca6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@
import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
import android.annotation.WorkerThread;
@@ -151,7 +152,9 @@
@Override
public void init() {
mLogger.logBatteryControllerInit(this, mHasReceivedBattery);
- registerReceiver();
+ if (!registerBatteryControllerReceiversInCorestartable()) {
+ registerReceiver();
+ }
if (!mHasReceivedBattery) {
// Get initial state. Relying on Sticky behavior until API for getting info.
Intent intent = mContext.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
new file mode 100644
index 0000000..7f601c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.os.PowerManager;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link CoreStartable} responsible for registering the receivers for
+ * {@link BatteryControllerImpl}.
+ */
+@SysUISingleton
+public class BatteryControllerStartable implements CoreStartable {
+
+ private final BatteryController mBatteryController;
+ private final Executor mBackgroundExecutor;
+
+ private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+ protected final BroadcastDispatcher mBroadcastDispatcher;
+ @Inject
+ public BatteryControllerStartable(
+ BatteryController batteryController,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Executor backgroundExecutor) {
+ mBatteryController = batteryController;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundExecutor = backgroundExecutor;
+ }
+
+ private void registerReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(ACTION_LEVEL_TEST);
+ filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+ mBroadcastDispatcher.registerReceiver((BroadcastReceiver) mBatteryController, filter);
+ }
+
+ @Override
+ public void start() {
+ if (registerBatteryControllerReceiversInCorestartable()
+ && mBatteryController instanceof BatteryControllerImpl) {
+ mBackgroundExecutor.execute(() -> registerReceiver());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e..f19fa20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -53,32 +55,40 @@
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+ private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
interactor.defaultActiveMediaSession
.flatMapLatest { session ->
if (session == null) {
- flowOf(null)
+ flowOf(Result.Data<SessionWithPlayback?>(null))
} else {
- mediaDeviceSessionInteractor.playbackState(session).map { playback ->
- playback?.let { SessionWithPlayback(session, it) }
- }
+ mediaDeviceSessionInteractor
+ .playbackState(session)
+ .map { playback ->
+ playback?.let {
+ Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+ }
+ }
+ .filterNotNull()
}
}
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- null,
+ Result.Loading(),
)
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
ConnectedDeviceViewModel(
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
context.getString(
R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
+ mediaDeviceSession.data.session.appLabel
)
} else {
context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
val icon =
currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
?: Icon.Resource(
@@ -130,6 +143,7 @@
fun onBarClick(expandable: Expandable) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+ val result = sessionWithPlayback.value
+ actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
new file mode 100644
index 0000000..ac8092c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.volume.panel.component.volume.domain.interactor
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides volume sliders to show in the Volume Panel. */
+@VolumePanelScope
+class AudioSlidersInteractor
+@Inject
+constructor(
+ @VolumePanelScope scope: CoroutineScope,
+ mediaOutputInteractor: MediaOutputInteractor,
+ audioRepository: AudioRepository,
+) {
+
+ val volumePanelSliders: StateFlow<List<SliderType>> =
+ combineTransform(
+ mediaOutputInteractor.activeMediaDeviceSessions,
+ mediaOutputInteractor.defaultActiveMediaSession,
+ audioRepository.communicationDevice,
+ ) { activeSessions, defaultSession, communicationDevice ->
+ coroutineScope {
+ val viewModels = buildList {
+ if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
+ addSession(activeSessions.remote)
+ addStream(AudioManager.STREAM_MUSIC)
+ } else {
+ addStream(AudioManager.STREAM_MUSIC)
+ addSession(activeSessions.remote)
+ }
+
+ if (communicationDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+ addStream(AudioManager.STREAM_BLUETOOTH_SCO)
+ } else {
+ addStream(AudioManager.STREAM_VOICE_CALL)
+ }
+ addStream(AudioManager.STREAM_RING)
+ addStream(AudioManager.STREAM_NOTIFICATION)
+ addStream(AudioManager.STREAM_ALARM)
+ }
+ emit(viewModels)
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
+ if (remoteMediaDeviceSession?.canAdjustVolume == true) {
+ add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
+ }
+ }
+
+ private fun MutableList<SliderType>.addStream(stream: Int) {
+ add(SliderType.Stream(AudioStream(stream)))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
index b97123b..6129ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.volume.domain.model
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
/** The type of volume slider that can be shown at the UI. */
sealed interface SliderType {
@@ -25,5 +26,5 @@
data class Stream(val stream: AudioStream) : SliderType
/** The represents media device casting volume. */
- data object MediaDeviceCast : SliderType
+ data class MediaDeviceCast(val session: MediaDeviceSession) : SliderType
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c8cd6fd..ee642a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -53,6 +53,7 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -61,6 +62,7 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -78,6 +80,8 @@
VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_VOICE_CALL) to
VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
+ VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_RING) to
VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 09e56c1..741f5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,12 +16,12 @@
package com.android.systemui.volume.panel.component.volume.ui.viewmodel
-import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -33,12 +33,12 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/**
@@ -55,28 +55,21 @@
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+ streamsInteractor: AudioSlidersInteractor,
) {
val sliderViewModels: StateFlow<List<SliderViewModel>> =
- combineTransform(
- mediaOutputInteractor.activeMediaDeviceSessions,
- mediaOutputInteractor.defaultActiveMediaSession,
- ) { activeSessions, defaultSession ->
+ streamsInteractor.volumePanelSliders
+ .transformLatest { sliderTypes ->
coroutineScope {
- val viewModels = buildList {
- if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- } else {
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
+ val viewModels =
+ sliderTypes.map { type ->
+ when (type) {
+ is SliderType.Stream -> createStreamViewModel(type.stream)
+ is SliderType.MediaDeviceCast ->
+ createSessionViewModel(type.session)
+ }
}
-
- addStreamViewModel(this, AudioManager.STREAM_VOICE_CALL)
- addStreamViewModel(this, AudioManager.STREAM_RING)
- addStreamViewModel(this, AudioManager.STREAM_NOTIFICATION)
- addStreamViewModel(this, AudioManager.STREAM_ALARM)
- }
emit(viewModels)
}
}
@@ -98,29 +91,18 @@
scope.launch { mutableIsExpanded.emit(isExpanded) }
}
- private fun CoroutineScope.addRemoteViewModelIfNeeded(
- list: MutableList<SliderViewModel>,
- remoteMediaDeviceSession: MediaDeviceSession?
- ) {
- if (remoteMediaDeviceSession?.canAdjustVolume == true) {
- val viewModel =
- castVolumeSliderViewModelFactory.create(
- remoteMediaDeviceSession,
- this,
- )
- list.add(viewModel)
- }
+ private fun CoroutineScope.createSessionViewModel(
+ session: MediaDeviceSession
+ ): CastVolumeSliderViewModel {
+ return castVolumeSliderViewModelFactory.create(session, this)
}
- private fun CoroutineScope.addStreamViewModel(
- list: MutableList<SliderViewModel>,
- stream: Int,
- ) {
- val viewModel =
- streamSliderViewModelFactory.create(
- AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
- this,
- )
- list.add(viewModel)
+ private fun CoroutineScope.createStreamViewModel(
+ stream: AudioStream,
+ ): AudioStreamSliderViewModel {
+ return streamSliderViewModelFactory.create(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
+ this,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
index b84b01e..8793538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.volume.panel.shared.model
-import javax.inject.Qualifier
+/** Models a loadable result */
+sealed interface Result<T> {
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+ /** The data is still loading */
+ class Loading<T> : Result<T>
+
+ /** The data is loaded successfully */
+ data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e48b639..263ddc1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
@@ -55,6 +56,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -124,6 +126,8 @@
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
+ private final CommunalTransitionViewModel mCommunalTransitionViewModel;
+ private final JavaAdapter mJavaAdapter;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -187,6 +191,8 @@
UserTracker userTracker,
DisplayTracker displayTracker,
NoteTaskInitializer noteTaskInitializer,
+ CommunalTransitionViewModel communalTransitionViewModel,
+ JavaAdapter javaAdapter,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -205,6 +211,8 @@
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
+ mCommunalTransitionViewModel = communalTransitionViewModel;
+ mJavaAdapter = javaAdapter;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -381,6 +389,8 @@
void initRecentTasks(RecentTasks recentTasks) {
recentTasks.addAnimationStateListener(mSysUiMainExecutor,
mCommandQueue::onRecentsAnimationStateChanged);
+ mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(),
+ recentTasks::setTransitionBackgroundColor);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index ffedb30..52af8fb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -18,11 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
index f8fdd8d..6512e70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
@@ -16,7 +16,7 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 08c1de1..303ae97 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -50,10 +50,11 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index b4a9d40..e2063d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
@@ -40,6 +39,8 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.clocks.ClockController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 93e7602..6228ff8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,12 +27,13 @@
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.Editable;
import android.text.TextWatcher;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index edb910a..c566826 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -23,12 +23,13 @@
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 6654a6c..a0e8065 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -17,7 +17,6 @@
import android.graphics.Color;
import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -27,10 +26,11 @@
import androidx.slice.SliceSpecs;
import androidx.slice.builders.ListBuilder;
import androidx.slice.widget.RowContent;
+import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.res.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
index e6b6964..ed61ee12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -1,8 +1,8 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertEquals
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index b2828a4..0696a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -31,11 +31,12 @@
import android.animation.AnimatorTestRule;
import android.platform.test.annotations.DisableFlags;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.Flags;
import com.android.systemui.animation.ViewHierarchyAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
index 17f77aa..3b57d8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -16,9 +16,9 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.power.shared.model.ScreenPowerState
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e5..afd2034 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,6 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fde45d3..68b6d9d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -106,11 +106,12 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 532c59a..d6a5b4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -28,8 +28,8 @@
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 12f334b..5bc9aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -16,49 +16,84 @@
package com.android.systemui.accessibility;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.window.InputTransferToken;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-
+ private static final long ANIMATION_DURATION_MS = 100L;
+ private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
+ private static final long ANIMATION_TIMEOUT_MS =
+ 5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
private FullscreenMagnificationController mFullscreenMagnificationController;
private SurfaceControlViewHost mSurfaceControlViewHost;
+ private ValueAnimator mShowHideBorderAnimator;
+ private SurfaceControl.Transaction mTransaction;
+ private TestableWindowManager mWindowManager;
@Before
public void setUp() {
getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
- new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- new InputTransferToken(), "FullscreenMagnification"));
-
+ spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification")));
Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = new TestableWindowManager(wm);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mTransaction = new SurfaceControl.Transaction();
+ mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
mFullscreenMagnificationController = new FullscreenMagnificationController(
mContext,
+ mContext.getMainExecutor(),
mContext.getSystemService(AccessibilityManager.class),
mContext.getSystemService(WindowManager.class),
- scvhSupplier);
+ scvhSupplier,
+ mTransaction,
+ mShowHideBorderAnimator);
}
@After
@@ -69,29 +104,143 @@
}
@Test
- public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true)
- );
-
- // Wait for Rects updated.
- waitForIdleSync();
+ public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
}
@Test
- public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> {
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true);
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(false);
+ public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
+ CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ disableAnimationEndLatch.countDown();
+ } else {
+ enableAnimationEndLatch.countDown();
}
- );
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for enabling animation to be finished",
+ enableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
- assertThat(mSurfaceControlViewHost.getView()).isNull();
+ getInstrumentation().runOnMainSync(() ->
+ // Disable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
+
+ assertTrue("Failed to wait for disabling animation to be finished",
+ disableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ verify(mSurfaceControlViewHost).release();
}
+ @Test
+ public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
+ throws InterruptedException {
+ // Simulate the hiding border animation is running
+ when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ }
+
+ @Test
+ public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ getInstrumentation().runOnMainSync(() ->
+ mFullscreenMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_SCREEN_SIZE));
+
+ int borderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ final int newWidth = testWindowBounds.width() + 2 * borderOffset;
+ final int newHeight = testWindowBounds.height() + 2 * borderOffset;
+ verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
+ }
+
+ private ValueAnimator newNullTargetObjectAnimator() {
+ final ValueAnimator animator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(ANIMATION_DURATION_MS);
+ return animator;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 41d5d5d..25e5470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -101,7 +101,7 @@
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 3b5cbea..6dc5b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -121,7 +121,8 @@
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+ mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
index b843fda..516b665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -23,11 +23,12 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Size;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 1576457..ebb6b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -39,8 +39,10 @@
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
@@ -88,6 +90,10 @@
@Mock
private LocalBluetoothAdapter mLocalBluetoothAdapter;
@Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private HapClientProfile mHapClientProfile;
+ @Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private BluetoothEventManager mBluetoothEventManager;
@@ -106,6 +112,8 @@
public void setUp() {
mTestableLooper = TestableLooper.get(this);
when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
@@ -163,6 +171,7 @@
private void setUpPairNewDeviceDialog() {
mDialogDelegate = new HearingDevicesDialogDelegate(
+ mContext,
true,
mSystemUIDialogFactory,
mActivityStarter,
@@ -185,6 +194,7 @@
private void setUpDeviceListDialog() {
mDialogDelegate = new HearingDevicesDialogDelegate(
+ mContext,
false,
mSystemUIDialogFactory,
mActivityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
index 8c8544c..d2c6957 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -90,7 +91,8 @@
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -115,7 +117,8 @@
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -134,11 +137,25 @@
}
@Test
+ public void testFingerprintReEnrollDialog_forced() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ true);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ verify(mDialog, never()).setNegativeButton(anyInt(), any());
+ }
+
+ @Test
public void testFaceReEnrollDialog_onRemovalSucceeded() {
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FACE));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -163,7 +180,8 @@
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FACE));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index c6771b2..a279d3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -18,7 +18,9 @@
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -98,7 +100,7 @@
public void setUp() {
when(mFingerprintReEnrollNotificationOptional.orElse(any()))
.thenReturn(mFingerprintReEnrollNotification);
- when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
mLooper = TestableLooper.get(this);
@@ -140,8 +142,10 @@
}
@Test
- public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_Optional() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+ FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(false);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
FINGERPRINT_ACQUIRED_RE_ENROLL,
@@ -160,6 +164,35 @@
assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
.isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+ BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isFalse();
+ }
+
+ @Test
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_force() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+ FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
+
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ FINGERPRINT_ACQUIRED_RE_ENROLL,
+ "Testing Fingerprint Re-enrollment" /* errString */,
+ BiometricSourceType.FINGERPRINT
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
+
+ assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
+ .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+ BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isTrue();
}
@Test
public void testShowFaceReEnrollNotification_onErrorReEnroll() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index c0e108e..5e7adb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.MotionEvent
import android.view.Surface
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index f5990be..b7ed27f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,7 +21,7 @@
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index eb6e517..2c17181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -24,7 +24,7 @@
import android.os.Looper
import android.os.PatternMatcher
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 39e4467..582f301 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -21,7 +21,7 @@
import android.content.IntentFilter
import android.os.Handler
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
index feaedc5..1e522fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.camera
import android.content.Intent
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 6a79ee8..6cc3ef19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.controls.ui
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.HapticFeedbackConstants
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 523127e0..dbe59e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -23,7 +23,7 @@
import android.content.res.Resources.NotFoundException
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
index 70d6dd9..943e212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
@@ -17,7 +17,7 @@
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 7c1325e..d500dd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 303aaa1..5e87a6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -19,7 +19,7 @@
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
index db6f85f..755cc46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 7d7abab..0fdda08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index e287f19..3c965ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.flags
import android.os.PowerManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 1f04828..0116e53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.power.domain.interactor.PowerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1d1949d..2daa86b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 4ba1bc6..8a29217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -2,7 +2,7 @@
import android.app.Fragment
import android.os.Looper
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index eb885fd..4fcd3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -18,9 +18,9 @@
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 711669e..bb95ba3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.fail
import org.junit.After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 37dea11..791563a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -16,12 +16,13 @@
package com.android.systemui.media.controls.ui.controller
-import android.test.suitebuilder.annotation.SmallTest
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.ui.view.MediaHost
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
index b4f5528..4101c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.notetask
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index e09c804..2c86a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -19,7 +19,7 @@
import android.app.role.RoleManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index ebd34de..231b333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -25,8 +25,8 @@
import android.hardware.input.InputSettings
import android.os.UserHandle
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 4547bff..9429725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,11 +42,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.UiEventLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2bdad2b..cae170f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -38,18 +38,19 @@
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.fuelgauge.Estimate;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 0275643..e50320d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,7 +1,7 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableResources
import android.view.ContextThemeWrapper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 718e302f..0abcc64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -42,7 +42,6 @@
import android.os.Looper;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -52,6 +51,7 @@
import android.widget.TextView;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 0eae5aa..98adbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -31,12 +31,12 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
index c43c3e6..29e2a8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
@@ -16,9 +16,9 @@
import static junit.framework.Assert.assertEquals;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 33f8f1f..ef979d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -31,7 +31,7 @@
import android.os.Parcel
import android.service.quicksettings.IQSTileService
import android.service.quicksettings.Tile
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.IWindowManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
index b8e6403..eb013c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
@@ -19,12 +19,12 @@
import android.content.res.ColorStateList;
import android.service.quicksettings.Tile;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 8142456..0a36ae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -56,9 +56,9 @@
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 28331bb..0ff29db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -34,8 +34,8 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index d011821..248af1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -34,11 +34,12 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 512ca53..ecbd0f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -385,7 +385,7 @@
}
@Test
- fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+ fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
val state = QSTile.State() // A state that handles longPress
// GIVEN an invalid long-press effect duration
@@ -399,7 +399,7 @@
}
@Test
- fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+ fun onStateChange_longPressEffectActive_withValidDuration_initializesEffect() {
// GIVEN a test state that handles long-press and a valid long-press effect duration
val state = QSTile.State()
@@ -420,7 +420,7 @@
tileView.changeState(state)
// THEN the view binder no longer binds the view to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
}
@Test
@@ -435,7 +435,7 @@
tileView.changeState(state)
// THEN the view is bounded to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNotNull()
+ assertThat(tileView.isLongPressEffectBound).isTrue()
}
@Test
@@ -451,7 +451,7 @@
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
@@ -470,7 +470,7 @@
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 440270b..c02fca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -27,13 +27,13 @@
import android.content.pm.PackageManager;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +43,7 @@
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -57,7 +58,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index c900463..0f37143 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.lang.IllegalStateException
+import java.util.function.Consumer
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -78,7 +79,7 @@
fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controllerFactory).create(eq(0), any())
@@ -107,7 +108,7 @@
fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(
createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
onSaved,
@@ -136,7 +137,7 @@
fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
testScope.runTest {
setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verifyNoMoreInteractions(controllerFactory)
@@ -154,7 +155,7 @@
display(TYPE_OVERLAY, id = 2),
display(TYPE_WIFI, id = 3)
)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controller0, times(4)).handleScreenshot(any(), any(), any())
@@ -165,7 +166,7 @@
fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -190,7 +191,7 @@
fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -217,7 +218,7 @@
fun executeScreenshots_allDisplaysFail_reportsFail() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -244,7 +245,7 @@
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onDestroy()
@@ -256,7 +257,7 @@
fun removeWindows_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.removeWindows()
@@ -270,7 +271,7 @@
fun onCloseSystemDialogsReceived_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -286,7 +287,7 @@
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
whenever(controller0.isPendingSharedTransition).thenReturn(true)
whenever(controller1.isPendingSharedTransition).thenReturn(false)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -304,7 +305,7 @@
val toBeReturnedByProcessor = ScreenshotData.forTesting()
requestProcessor.toReturn = toBeReturnedByProcessor
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
assertThat(requestProcessor.processed)
@@ -321,7 +322,7 @@
fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -338,7 +339,7 @@
fun executeScreenshots_errorFromProcessor_logsUiError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -355,7 +356,7 @@
fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -368,7 +369,7 @@
fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -381,7 +382,7 @@
fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -401,7 +402,7 @@
fun executeScreenshots_errorFromScreenshotController_reportsError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -421,7 +422,7 @@
fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -434,6 +435,25 @@
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ var onSavedCallCount = 0
+ val onSaved: (Uri?) -> Unit = {
+ assertThat(it).isNull()
+ onSavedCallCount += 1
+ }
+ whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer {
+ (it.getArgument(1) as Consumer<Uri?>).accept(null)
+ }
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+ assertThat(onSavedCallCount).isEqualTo(1)
+
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 0776aa7..77b5c91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -232,7 +232,7 @@
override fun onCloseSystemDialogsReceived() {}
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback,
) {
requestReceived = screenshotRequest
@@ -248,7 +248,7 @@
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback,
) {
runBlocking {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 621b058..254f1e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -188,6 +188,18 @@
* actual values returned by ActivityTaskManager
*/
object RootTasks {
+ /** An empty RootTaskInfo with no child tasks. */
+ val emptyWithNoChildTasks =
+ newRootTaskInfo(
+ taskId = 2,
+ visible = true,
+ running = true,
+ numActivities = 0,
+ bounds = FULL_SCREEN,
+ ) {
+ emptyList()
+ }
+
/**
* The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when
* no other visible activities are in split mode
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
new file mode 100644
index 0000000..9e3ae05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE_PIP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class PrivateProfilePolicyTest {
+ private val kosmos = Kosmos()
+ private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository)
+
+ // TODO:
+ // private app in PIP
+ // private app below personal PIP app
+ // Freeform windows
+
+ @Test
+ fun shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ spec = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.SHADE_EXPANDED))
+ }
+
+ @Test
+ fun noPrivate_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.NO_VISIBLE_TASKS))
+ }
+
+ @Test
+ fun withPrivateFullScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotVisible_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks =
+ listOf(
+ fullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ visible = true
+ ),
+ fullScreen(
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ visible = false
+ ),
+ launcher(visible = false),
+ emptyRootSplit,
+ )
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.NO_VISIBLE_TASKS,
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivatePictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateAppBelowPictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PRIVATE),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
new file mode 100644
index 0000000..5d35528
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFormApps
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.DESKTOP_MODE_ENABLED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.SHADE_EXPANDED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_IS_TOP
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_NOT_TOP
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class WorkProfilePolicyTest {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private val kosmos = Kosmos()
+ private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+
+ /**
+ * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
+ * the case where the RootTaskInfo would match but child tasks are empty.
+ */
+ @Test
+ fun withEmptyChildTasks_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks = listOf(RootTasks.emptyWithNoChildTasks)
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun noWorkApp_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ SHADE_EXPANDED,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_matched() = runTest {
+ val result =
+ policy.check(singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)))
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFocusedInSplitScreen_matched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = SPLIT_TOP),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkNotFocusedInSplitScreen_notMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkBelowPersonalPictureInPicture_matched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_matched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_desktopModeEnabled_notMatched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ DESKTOP_MODE_ENABLED,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e611da0..ee03236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -85,6 +86,7 @@
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var dialogFactory: SystemUIDialogFactory
+ @Mock private lateinit var communalColors: CommunalColors
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var keyguardInteractor: KeyguardInteractor
@@ -116,6 +118,7 @@
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
testableLooper = TestableLooper.get(this)
@@ -156,6 +159,7 @@
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0a8e470..7a39a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -154,6 +154,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -161,6 +162,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -358,6 +360,10 @@
protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
+ protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
+ new FakeHeadsUpNotificationRepository();
+ protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
+ new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
protected SysuiStatusBarStateController mStatusBarStateController;
@@ -384,7 +390,6 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
@@ -730,6 +735,7 @@
mActivityStarter,
mSharedNotificationContainerInteractor,
mActiveNotificationsInteractor,
+ mHeadsUpNotificationInteractor,
mShadeAnimationInteractor,
mKeyguardViewConfigurator,
mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 650c45b..81e20c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -46,6 +46,7 @@
import android.animation.ValueAnimator;
import android.graphics.Point;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -62,6 +63,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1287,6 +1289,7 @@
}
@Test
+ @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
public void shadeExpanded_whenHunIsPresent() {
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 4df7ef5..6631d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
@@ -29,10 +32,14 @@
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceUntilIdle
@@ -235,4 +242,41 @@
val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
assertThat(bottomAreaAlpha).isEqualTo(1f)
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsPresent() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a pinned heads up is present
+ mFakeHeadsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository("key", isPinned = true)
+ )
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a heads up is animating away
+ mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
+ FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
+ this.isPinned.value = isPinned
+ }
}
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 8c5a4d0..da09579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -38,8 +38,6 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -166,8 +164,6 @@
.thenReturn(emptyFlow<TransitionStep>())
featureFlagsClassic = FakeFeatureFlagsClassic()
- featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
- featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
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 ba8eb6f..f380b6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -155,8 +155,6 @@
.thenReturn(emptyFlow())
val featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
- featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index 8841f48..3cb48d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -15,7 +15,7 @@
package com.android.systemui.shared.animation
import android.graphics.Point
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Surface.ROTATION_0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index c39b29f..e9222c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -37,8 +37,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 02954b8..7ddf7a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -26,9 +26,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index 16eb1d9..b18b7f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.commandline
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 5bc75e8..7e88ae0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.connectivity
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 44e3bb4..7bd77a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -22,14 +22,14 @@
import android.os.HandlerThread;
import android.telephony.SubscriptionInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
index a226ded..7aed4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -15,8 +15,8 @@
*/
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index f667b83..461d804 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -39,11 +39,12 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index f6f939a..3bbf06d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -19,10 +19,11 @@
import static junit.framework.Assert.assertEquals;
import android.net.NetworkCapabilities;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 375ca063..35609a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -33,17 +33,18 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
index 9e73487..5bf0a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 2b94561..6b2ee76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -19,12 +19,13 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.widget.FrameLayout;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index fda8f51..fc4702c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -42,10 +42,11 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index 0e24ed4..eafa78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -25,13 +25,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index a6381d1..5b72ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -25,10 +25,11 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -38,7 +39,6 @@
import org.junit.Before;
import org.junit.Test;
-
@SmallTest
@org.junit.runner.RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index 2ef4374..2d8e692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -30,7 +30,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.util.FloatProperty;
@@ -38,6 +37,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 9c20e54..ffb8646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,7 +45,6 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.view.LayoutInflater;
@@ -53,6 +52,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 3c1f559..97cb11e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -65,7 +65,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -73,11 +72,13 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index f31b1c4..13ced92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -55,20 +55,20 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.UiThreadTest;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -82,8 +82,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 0a15f0d..4a91cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar.notification.row;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableResources;
import android.testing.UiThreadTest;
import android.util.KeyValueListParser;
-import com.android.systemui.res.R;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
@@ -33,12 +40,6 @@
import java.util.ArrayList;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.mock;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@UiThreadTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index ccedd36..51665d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -41,7 +41,6 @@
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.SpannableString;
@@ -50,10 +49,12 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,8 +66,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
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 912ecb3..3a427f3 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
@@ -22,6 +22,8 @@
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,8 +38,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -56,8 +56,6 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -126,7 +124,6 @@
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -193,8 +190,6 @@
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
-
when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
}
@@ -299,36 +294,11 @@
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(true);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
- // view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
- mController.setBouncerShowingFromCentralSurfaces(false);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -343,36 +313,11 @@
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(false);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
- // empty view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
- mController.setBouncerShowingFromCentralSurfaces(true);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -1018,7 +963,6 @@
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
- mFeatureFlags,
mNotificationTargetsHelper,
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 1e058ca..89ae9f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -90,6 +90,7 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -106,6 +107,7 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Tests for {@link NotificationStackScrollLayout}.
@@ -1044,6 +1046,96 @@
assertFalse(mStackScroller.getIsBeingDragged());
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN headsUpAnimatingAway is true
+ verify(headsUpAnimatingAwayListener).accept(true);
+ assertTrue(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL would be ready for HUN animations, BUT it is expanded
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ assertTrue("Should be expanded by default.", mStackScroller.isExpanded());
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+ // BUT there is a pending appear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // THEN headsUpAnimatingWay is not set
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // AND there is a HUN animating away
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
+
+ // WHEN the child animations are finished
+ mStackScroller.onChildAnimationFinished();
+
+ // THEN headsUpAnimatingAway is false
+ verify(headsUpAnimatingAwayListener).accept(false);
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
private MotionEvent captureTouchSentToSceneFramework() {
ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
@@ -1056,6 +1148,14 @@
mStackScroller.setStatusBarState(state);
}
+ private void prepareStackScrollerForHunAnimations(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setIsExpanded(false);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
private ExpandableNotificationRow createClearableRow() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
@@ -1116,4 +1216,6 @@
assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
+
+ private abstract static class BooleanConsumer implements Consumer<Boolean> { }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e9ec323..f49dc98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -37,12 +37,13 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
import android.view.ViewRootImpl;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 6fecbb0..7cb41f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -34,8 +34,8 @@
import android.os.Handler;
import android.os.PowerManager;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 6150253..4dd97bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b7a3b30..b5525b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -26,6 +26,7 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
import android.os.ParcelUuid
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
@@ -46,8 +47,10 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -64,10 +67,14 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -97,7 +104,11 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val flags =
- FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -114,9 +125,17 @@
@Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var wifiManager: WifiManager
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock private lateinit var wifiTableLogBuffer: TableLogBuffer
private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+ private val wifiPickerTrackerCallback =
+ argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -139,6 +158,9 @@
mock<TableLogBuffer>()
}
+ whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+ .thenReturn(wifiPickerTracker)
+
// For convenience, set up the subscription info callbacks
whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
when (invocation.getArgument(0) as Int) {
@@ -165,15 +187,14 @@
wifiRepository =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- mock(),
- mock(),
- FakeExecutor(FakeSystemClock()),
- dispatcher,
+ flags,
testScope.backgroundScope,
- mock(),
+ mainExecutor,
+ dispatcher,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ wifiLogBuffer,
+ wifiTableLogBuffer,
)
carrierConfigRepository =
@@ -278,7 +299,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -291,7 +312,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -445,7 +466,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -462,7 +483,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -479,7 +500,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -491,7 +512,7 @@
// WHEN the wifi network updates to be not carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
runCurrent()
// THEN the repos update
@@ -507,7 +528,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -520,7 +541,7 @@
// WHEN the wifi network updates to be carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
runCurrent()
// THEN the repos update
@@ -562,7 +583,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -845,7 +866,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -867,7 +888,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -890,7 +911,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -912,7 +933,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -946,7 +967,7 @@
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -982,7 +1003,7 @@
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -1005,7 +1026,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is true
assertThat(latest).isTrue()
@@ -1026,7 +1047,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
// takes precedence over the wifi network being carrier merged.)
@@ -1048,7 +1069,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// AND we're in airplane mode
airplaneModeRepository.setIsAirplaneMode(true)
@@ -1277,12 +1298,26 @@
return callbackCaptor.value!!
}
- // Note: This is used to update the [WifiRepository].
- private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
- runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ private fun setWifiState(isCarrierMerged: Boolean) {
+ if (isCarrierMerged) {
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ } else {
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
+ }
+ wifiPickerTrackerCallback.value.onWifiEntriesChanged()
}
private fun TestScope.getSubscriptionCallback():
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 3126362..f8d50f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -68,14 +68,14 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+class WifiRepositoryImplTest : SysuiTestCase() {
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
// parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
// inside each test case without needing to manually recreate the repository.
- private val underTest: WifiRepositoryViaTrackerLib by lazy {
- WifiRepositoryViaTrackerLib(
+ private val underTest: WifiRepositoryImpl by lazy {
+ WifiRepositoryImpl(
featureFlags,
testScope.backgroundScope,
executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
new file mode 100644
index 0000000..f1dbee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BatteryControllerStartableTest extends SysuiTestCase {
+
+ private BatteryController mBatteryController;
+ private BatteryControllerStartable mBatteryControllerStartable;
+ private MockitoSession mMockitoSession;
+ private FakeExecutor mExecutor;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+
+ @Before
+ public void setUp() throws IllegalStateException {
+ MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(BatterySaverUtils.class)
+ .startMocking();
+
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ mBatteryController = new BatteryControllerImpl(getContext(),
+ mock(EnhancedEstimates.class),
+ mock(PowerManager.class),
+ mock(BroadcastDispatcher.class),
+ mock(DemoModeController.class),
+ mock(DumpManager.class),
+ mock(BatteryControllerLogger.class),
+ new Handler(Looper.getMainLooper()),
+ new Handler(Looper.getMainLooper()));
+ mBatteryController.init();
+
+ mBatteryControllerStartable = new BatteryControllerStartable(mBatteryController,
+ mBroadcastDispatcher, mExecutor);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagEnabled_registersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verify(mBroadcastDispatcher).registerReceiver(any(), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagDisabled_doesNotRegistersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verifyZeroInteractions(mBroadcastDispatcher);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index a5c766d..9d4f1fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -36,11 +36,12 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.StaticInOrder;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 777fa28..1c54263 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -20,7 +20,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.impl.CameraMetadataNative
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index cb6ce68..9bb7607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -44,8 +44,8 @@
import android.os.Handler;
import android.os.UserManager;
import android.security.IKeyChainService;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index dc0d07c..b9557d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -23,14 +23,15 @@
import android.app.RemoteInput;
import android.os.Handler;
import android.provider.DeviceConfig;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
new file mode 100644
index 0000000..b1df159
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.surfaceeffects.revealeffect
+
+import android.graphics.RenderEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.model.SysUiStateTest
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RippleRevealEffectTest : SysUiStateTest() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+ @Test
+ fun play_triggersDrawCallback() {
+ var effectFromCallback: RenderEffect? = null
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {
+ effectFromCallback = renderEffect
+ }
+ }
+ val revealEffect = RippleRevealEffect(revealEffectConfig, drawCallback)
+ assertThat(effectFromCallback).isNull()
+
+ revealEffect.play()
+
+ animatorTestRule.advanceTimeBy(500L)
+
+ assertThat(effectFromCallback).isNotNull()
+ }
+
+ @Test
+ fun play_triggersStateChangedCallback() {
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {}
+ }
+ var animationStartedCalled = false
+ var animationEndedCalled = false
+ val stateChangedCallback =
+ object : RippleRevealEffect.AnimationStateChangedCallback {
+ override fun onAnimationStart() {
+ animationStartedCalled = true
+ }
+
+ override fun onAnimationEnd() {
+ animationEndedCalled = true
+ }
+ }
+ val revealEffect =
+ RippleRevealEffect(revealEffectConfig, drawCallback, stateChangedCallback)
+
+ assertThat(animationStartedCalled).isFalse()
+ assertThat(animationEndedCalled).isFalse()
+
+ revealEffect.play()
+
+ assertThat(animationStartedCalled).isTrue()
+
+ animatorTestRule.advanceTimeBy(revealEffectConfig.duration.toLong())
+
+ assertThat(animationEndedCalled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
index a85ae7df..35f9c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -3,7 +3,7 @@
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
index 900d792..2436725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
@@ -22,9 +22,9 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index b10f16c..ab52c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -25,9 +25,10 @@
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index b0bd83e..741b2e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -27,10 +27,11 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.media.AudioManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
deleted file mode 100644
index d2c8aea..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.wmshell;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.notetask.NoteTaskInitializer;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedEventCallback;
-import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.sysui.ShellInterface;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-/**
- * Tests for {@link WMShell}.
- *
- * Build/Install/Run:
- * atest SystemUITests:WMShellTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WMShellTest extends SysuiTestCase {
- WMShell mWMShell;
-
- @Mock ShellInterface mShellInterface;
- @Mock CommandQueue mCommandQueue;
- @Mock ConfigurationController mConfigurationController;
- @Mock KeyguardStateController mKeyguardStateController;
- @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock ScreenLifecycle mScreenLifecycle;
- @Mock SysUiState mSysUiState;
- @Mock Pip mPip;
- @Mock SplitScreen mSplitScreen;
- @Mock OneHanded mOneHanded;
- @Mock WakefulnessLifecycle mWakefulnessLifecycle;
- @Mock UserTracker mUserTracker;
- @Mock ShellExecutor mSysUiMainExecutor;
- @Mock NoteTaskInitializer mNoteTaskInitializer;
- @Mock DesktopMode mDesktopMode;
- @Mock RecentTasks mRecentTasks;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mWMShell = new WMShell(
- mContext,
- mShellInterface,
- Optional.of(mPip),
- Optional.of(mSplitScreen),
- Optional.of(mOneHanded),
- Optional.of(mDesktopMode),
- Optional.of(mRecentTasks),
- mCommandQueue,
- mConfigurationController,
- mKeyguardStateController,
- mKeyguardUpdateMonitor,
- mScreenLifecycle,
- mSysUiState,
- mWakefulnessLifecycle,
- mUserTracker,
- displayTracker,
- mNoteTaskInitializer,
- mSysUiMainExecutor
- );
- }
-
- @Test
- public void initPip_registersCommandQueueCallback() {
- mWMShell.initPip(mPip);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- }
-
- @Test
- public void initOneHanded_registersCallbacks() {
- mWMShell.initOneHanded(mOneHanded);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
- verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
- verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
- }
-
- @Test
- public void initDesktopMode_registersListener() {
- mWMShell.initDesktopMode(mDesktopMode);
- verify(mDesktopMode).addVisibleTasksListener(
- any(DesktopModeTaskRepository.VisibleTasksListener.class),
- any(Executor.class));
- }
-
- @Test
- public void initRecentTasks_registersListener() {
- mWMShell.initRecentTasks(mRecentTasks);
- verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any());
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 0682361..42b6e18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -30,6 +30,7 @@
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -60,6 +61,7 @@
TestMocksModule::class,
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
+ DefaultBlueprintModule::class,
SceneContainerFrameworkModule::class,
FaceWakeUpTriggersConfigModule::class,
]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 8b1a1d9..e2386a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,11 +16,13 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.keyguard.logging.DeviceEntryIconLogger
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -29,5 +31,6 @@
deviceEntryIconViewModel = deviceEntryIconViewModel,
alternateBouncerInteractor = alternateBouncerInteractor,
systemUIDialogManager = systemUIDialogManager,
+ logger = mock<DeviceEntryIconLogger>(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e36ddc1..e3c218d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.util.communalColors
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
@@ -37,5 +38,6 @@
glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ communalColors = communalColors,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
index 7ca7030..e76cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open 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,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.communal.util
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
+val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors }
+val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
new file mode 100644
index 0000000..7046658
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.graphics.Color
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCommunalColors : CommunalColors {
+ private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK))
+
+ override val backgroundColor: StateFlow<Color>
+ get() = _backgroundColor.asStateFlow()
+
+ fun setBackgroundColor(color: Color) {
+ _backgroundColor.value = color
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 636d509..24603ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -19,7 +19,6 @@
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor, testScope) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index dc1b9fe..7bf77e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,4 +30,7 @@
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
+ override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ isHeadsUpAnimatingAway.value = animatingAway
+ }
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8ab2e0f..eb2ef29 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -91,6 +91,16 @@
}
flag {
+ name: "focus_click_point_window_bounds_from_a11y_window_info"
+ namespace: "accessibility"
+ description: "Uses A11yWindowInfo bounds for focus click point bounds checking"
+ bug: "317166487"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fullscreen_fling_gesture"
namespace: "accessibility"
description: "When true, adds a fling gesture animation for fullscreen magnification"
@@ -144,3 +154,12 @@
description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
bug: "295575684"
}
+flag {
+ name: "send_hover_events_based_on_event_stream"
+ namespace: "accessibility"
+ description: "Send hover enter and exit based on the state of the hover event stream rather than the internal state of the touch explorer state machine. Because of the nondeterministic nature of gesture detection when done in talkback, relying on the internal state can cause crashes."
+ bug: "314251047"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ccf9a90..fc0fb5b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,7 +16,15 @@
package com.android.server.accessibility;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.INJECT_EVENTS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACCESSIBILITY;
+import static android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE;
+import static android.Manifest.permission.MODIFY_ACCESSIBILITY_DATA;
+import static android.Manifest.permission.RETRIEVE_WINDOW_CONTENT;
+import static android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
@@ -45,7 +53,6 @@
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import android.Manifest;
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -53,9 +60,11 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.MagnificationConfig;
import android.accessibilityservice.TouchInteractionController;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresNoPermission;
import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.AlertDialog;
@@ -95,6 +104,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -204,7 +214,6 @@
* event dispatch for {@link AccessibilityEvent}s generated across all processes
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
-@SuppressWarnings("MissingPermissionAnnotation")
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
AccessibilityUserState.ServiceInfoChangeListener,
@@ -479,7 +488,9 @@
AccessibilityDisplayListener a11yDisplayListener,
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
- ProxyManager proxyManager) {
+ ProxyManager proxyManager,
+ PermissionEnforcer permissionEnforcer) {
+ super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -514,6 +525,7 @@
* @param context A {@link Context} instance.
*/
public AccessibilityManagerService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -627,6 +639,7 @@
}
@Override
+ @RequiresNoPermission
public IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec(
int windowId) {
IAccessibilityManager.WindowTransformationSpec windowTransformationSpec =
@@ -723,8 +736,7 @@
void setBindInstantServiceAllowed(int userId, boolean allowed) {
mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "setBindInstantServiceAllowed");
+ MANAGE_BIND_INSTANT_SERVICE, "setBindInstantServiceAllowed");
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(userId);
if (allowed != userState.getBindInstantServiceAllowedLocked()) {
@@ -1120,6 +1132,7 @@
}
@Override
+ @RequiresNoPermission
public long addClient(IAccessibilityManagerClient callback, int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".addClient", FLAGS_ACCESSIBILITY_MANAGER,
@@ -1183,6 +1196,7 @@
}
@Override
+ @RequiresNoPermission
public boolean removeClient(IAccessibilityManagerClient callback, int userId) {
// TODO(b/190216606): Add tracing for removeClient when implementation is the same in master
@@ -1211,6 +1225,7 @@
}
@Override
+ @RequiresNoPermission
public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", FLAGS_ACCESSIBILITY_MANAGER,
@@ -1327,12 +1342,13 @@
* system action.
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void registerSystemAction(RemoteAction action, int actionId) {
+ registerSystemAction_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
getSystemActionPerformer().registerSystemAction(actionId, action);
}
@@ -1342,12 +1358,14 @@
* system action.
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void unregisterSystemAction(int actionId) {
+ unregisterSystemAction_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+
getSystemActionPerformer().unregisterSystemAction(actionId);
}
@@ -1360,6 +1378,7 @@
}
@Override
+ @RequiresNoPermission
public ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -1403,6 +1422,7 @@
}
@Override
+ @RequiresNoPermission
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -1445,6 +1465,7 @@
}
@Override
+ @RequiresNoPermission
public void interrupt(int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".interrupt",
@@ -1498,6 +1519,7 @@
}
@Override
+ @RequiresNoPermission
public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
IAccessibilityInteractionConnection connection, String packageName,
int userId) throws RemoteException {
@@ -1513,6 +1535,7 @@
}
@Override
+ @RequiresNoPermission
public void removeAccessibilityInteractionConnection(IWindow window) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection",
@@ -1522,23 +1545,25 @@
}
@Override
+ @EnforcePermission(MODIFY_ACCESSIBILITY_DATA)
public void setPictureInPictureActionReplacingConnection(
IAccessibilityInteractionConnection connection) throws RemoteException {
+ setPictureInPictureActionReplacingConnection_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection",
FLAGS_ACCESSIBILITY_MANAGER, "connection=" + connection);
}
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA,
- SET_PIP_ACTION_REPLACEMENT);
mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection);
}
@Override
+ @EnforcePermission(RETRIEVE_WINDOW_CONTENT)
public void registerUiTestAutomationService(IBinder owner,
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo,
int userId,
int flags) {
+ registerUiTestAutomationService_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
FLAGS_ACCESSIBILITY_MANAGER,
@@ -1546,9 +1571,6 @@
+ ";accessibilityServiceInfo=" + accessibilityServiceInfo + ";flags=" + flags);
}
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
- FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
-
synchronized (mLock) {
changeCurrentUserForTestAutomationIfNeededLocked(userId);
mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
@@ -1560,6 +1582,7 @@
}
@Override
+ @RequiresNoPermission
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
@@ -1619,15 +1642,14 @@
}
@Override
+ @EnforcePermission(RETRIEVE_WINDOW_CONTENT)
public IBinder getWindowToken(int windowId, int userId) {
+ getWindowToken_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
FLAGS_ACCESSIBILITY_MANAGER, "windowId=" + windowId + ";userId=" + userId);
}
- mSecurityPolicy.enforceCallingPermission(
- Manifest.permission.RETRIEVE_WINDOW_TOKEN,
- GET_WINDOW_TOKEN);
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -1663,18 +1685,15 @@
* specified target.
*/
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
+ notifyAccessibilityButtonClicked_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
FLAGS_ACCESSIBILITY_MANAGER,
"displayId=" + displayId + ";targetName=" + targetName);
}
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold permission "
- + android.Manifest.permission.STATUS_BAR_SERVICE);
- }
if (targetName == null) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
@@ -1694,37 +1713,27 @@
* user, {@code false} otherwise
*/
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+ notifyAccessibilityButtonVisibilityChanged_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE);
synchronized (mLock) {
notifyAccessibilityButtonVisibilityChangedLocked(shown);
}
}
@Override
- @RequiresPermission(allOf = {
- Manifest.permission.STATUS_BAR_SERVICE,
- Manifest.permission.MANAGE_ACCESSIBILITY
- })
+ @EnforcePermission(allOf = { STATUS_BAR_SERVICE, MANAGE_ACCESSIBILITY })
public void notifyQuickSettingsTilesChanged(
@UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+ notifyQuickSettingsTilesChanged_enforcePermission();
if (!android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
-
- mContext.enforceCallingPermission(
- Manifest.permission.STATUS_BAR_SERVICE,
- /* function= */ "notifyQuickSettingsTilesChanged");
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY,
- /* function= */ "notifyQuickSettingsTilesChanged");
-
if (DEBUG) {
Slog.d(LOG_TAG, TextUtils.formatSimple(
"notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
@@ -3953,19 +3962,15 @@
* class implementing a supported accessibility feature, or {@code null} if there's no
* specified target.
*/
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
@Override
public void performAccessibilityShortcut(String targetName) {
+ performAccessibilityShortcut_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".performAccessibilityShortcut",
FLAGS_ACCESSIBILITY_MANAGER, "targetName=" + targetName);
}
- if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
- && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
- != PackageManager.PERMISSION_GRANTED)) {
- throw new SecurityException(
- "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission");
- }
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
Display.DEFAULT_DISPLAY, UserShortcutType.HARDWARE, targetName));
@@ -4172,16 +4177,11 @@
* @hide
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void enableShortcutsForTargets(
boolean enable, @UserShortcutType int shortcutTypes,
@NonNull List<String> shortcutTargets, @UserIdInt int userId) {
- if (android.view.accessibility.Flags.migrateEnableShortcuts()) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
- } else {
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
- }
+ enableShortcutsForTargets_enforcePermission();
for (int shortcutType : USER_SHORTCUT_TYPES) {
if ((shortcutTypes & shortcutType) == shortcutType) {
enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
@@ -4376,10 +4376,9 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public Bundle getA11yFeatureToTileMap(@UserIdInt int userId) {
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "getA11yFeatureToTileMap");
-
+ getA11yFeatureToTileMap_enforcePermission();
Bundle bundle = new Bundle();
Map<ComponentName, ComponentName> a11yFeatureToTile =
getA11yFeatureToTileMapInternal(userId);
@@ -4435,17 +4434,13 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public List<String> getAccessibilityShortcutTargets(@UserShortcutType int shortcutType) {
+ getAccessibilityShortcutTargets_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
}
-
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
- }
return getAccessibilityShortcutTargetsInternal(shortcutType);
}
@@ -4536,6 +4531,7 @@
* doesn't.
*/
@Override
+ @RequiresNoPermission
public boolean sendFingerprintGesture(int gestureKeyCode) {
if (mTraceManager.isA11yTracingEnabledForTypes(
FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT)) {
@@ -4546,6 +4542,8 @@
synchronized(mLock) {
if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ // TODO(b/333547153) remove the AIDL definitions for these functions that are
+ // restricted to system server and move them to AccessibilityManagerInternal.
throw new SecurityException("Only SYSTEM can call sendFingerprintGesture");
}
}
@@ -4564,6 +4562,7 @@
* registered.
*/
@Override
+ @RequiresNoPermission
public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityWindowId",
@@ -4586,6 +4585,7 @@
* integer for non-interactive one.
*/
@Override
+ @RequiresNoPermission
public long getRecommendedTimeoutMillis() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(
@@ -4610,8 +4610,10 @@
}
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void setMagnificationConnection(
IMagnificationConnection connection) throws RemoteException {
+ setMagnificationConnection_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(
FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION)) {
mTraceManager.logTrace(LOG_TAG + ".setMagnificationConnection",
@@ -4619,10 +4621,21 @@
"connection=" + connection);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE);
-
getMagnificationConnectionManager().setConnection(connection);
+
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && connection == null
+ && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ // Since the connection does not exist, the system ui cannot provide the border
+ // implementation for fullscreen magnification. So we call reset to deactivate the
+ // fullscreen magnification to prevent the magnified but no border situation.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ getMagnificationController().getFullScreenMagnificationController()
+ .reset(display.getDisplayId(), false);
+ }
+ }
}
/**
@@ -4646,6 +4659,7 @@
}
@Override
+ @RequiresNoPermission
public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".associateEmbeddedHierarchy",
@@ -4658,6 +4672,7 @@
}
@Override
+ @RequiresNoPermission
public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy",
@@ -4674,6 +4689,7 @@
* @return The stroke width.
*/
@Override
+ @RequiresNoPermission
public int getFocusStrokeWidth() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4695,6 +4711,7 @@
* @return The color.
*/
@Override
+ @RequiresNoPermission
public int getFocusColor() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4716,6 +4733,7 @@
* @return {@code true} if the audio description is enabled, {@code false} otherwise.
*/
@Override
+ @RequiresNoPermission
public boolean isAudioDescriptionByDefaultEnabled() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".isAudioDescriptionByDefaultEnabled",
@@ -4738,6 +4756,7 @@
* @param attributes The accessibility window attributes.
*/
@Override
+ @RequiresNoPermission
public void setAccessibilityWindowAttributes(int displayId, int windowId, int userId,
AccessibilityWindowAttributes attributes) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -4749,34 +4768,30 @@
}
@Override
- @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+ @EnforcePermission(SET_SYSTEM_AUDIO_CAPTION)
public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
- "setSystemAudioCaptioningEnabled");
-
+ setSystemAudioCaptioningEnabled_enforcePermission();
mCaptioningManagerImpl.setSystemAudioCaptioningEnabled(isEnabled, userId);
}
@Override
+ @RequiresNoPermission
public boolean isSystemAudioCaptioningUiEnabled(int userId) {
return mCaptioningManagerImpl.isSystemAudioCaptioningUiEnabled(userId);
}
@Override
- @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+ @EnforcePermission(SET_SYSTEM_AUDIO_CAPTION)
public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
- "setSystemAudioCaptioningUiEnabled");
-
+ setSystemAudioCaptioningUiEnabled_enforcePermission();
mCaptioningManagerImpl.setSystemAudioCaptioningUiEnabled(isEnabled, userId);
}
@Override
+ @EnforcePermission(CREATE_VIRTUAL_DEVICE)
public boolean registerProxyForDisplay(IAccessibilityServiceClient client, int displayId)
throws RemoteException {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ registerProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
if (client == null) {
return false;
@@ -4812,8 +4827,9 @@
}
@Override
+ @EnforcePermission(CREATE_VIRTUAL_DEVICE)
public boolean unregisterProxyForDisplay(int displayId) {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ unregisterProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
final long identity = Binder.clearCallingIdentity();
try {
@@ -4828,6 +4844,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationSequence(String opPkg,
@FlashNotificationReason int reason, IBinder token) {
final long identity = Binder.clearCallingIdentity();
@@ -4840,6 +4857,7 @@
}
@Override
+ @RequiresNoPermission
public boolean stopFlashNotificationSequence(String opPkg) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4850,6 +4868,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationEvent(String opPkg,
@FlashNotificationReason int reason, String reasonPkg) {
final long identity = Binder.clearCallingIdentity();
@@ -4862,6 +4881,7 @@
}
@Override
+ @RequiresNoPermission
public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4903,6 +4923,7 @@
}
@Override
+ @RequiresNoPermission
public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
// The accessibility service is allowed. Don't show the restricted dialog.
if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
@@ -4936,8 +4957,9 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ isAccessibilityServiceWarningRequired_enforcePermission();
final ComponentName componentName = info.getComponentName();
// Warning is not required if the service is already enabled.
@@ -4983,6 +5005,7 @@
}
@Override
+ @PermissionManuallyEnforced // DUMP
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
@@ -5125,6 +5148,7 @@
}
@Override
+ @RequiresNoPermission
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
@@ -5234,7 +5258,14 @@
//Clip to the window bounds.
Rect windowBounds = mTempRect1;
- getWindowBounds(focus.getWindowId(), windowBounds);
+ if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
+ AccessibilityWindowInfo window = focus.getWindow();
+ if (window != null) {
+ window.getBoundsInScreen(windowBounds);
+ }
+ } else {
+ getWindowBounds(focus.getWindowId(), windowBounds);
+ }
if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
return false;
}
@@ -6138,9 +6169,9 @@
}
@Override
+ @EnforcePermission(INJECT_EVENTS)
public void injectInputEventToInputFilter(InputEvent event) {
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS,
- "injectInputEventToInputFilter");
+ injectInputEventToInputFilter_enforcePermission();
synchronized (mLock) {
final long endMillis =
SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
@@ -6225,11 +6256,11 @@
}
}
+ /** Used to attach accessibility overlays from the system itself i.e. magnification. */
+ @EnforcePermission(INTERNAL_SYSTEM_WINDOW)
@Override
- public void attachAccessibilityOverlayToDisplay(
- int displayId, SurfaceControl sc) {
- mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
+ public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+ attachAccessibilityOverlayToDisplay_enforcePermission();
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
@@ -6240,6 +6271,7 @@
null));
}
+ /** Called by services to attach accessibility overlays. */
@Override
public void attachAccessibilityOverlayToDisplay(
int interactionId,
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index b119d7d..853b824 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import android.accessibilityservice.AccessibilityService;
+import android.annotation.RequiresNoPermission;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,7 +35,6 @@
* If we are stripping and/or replacing the actions from a window, we need to intercept the
* nodes heading back to the service and swap out the actions.
*/
-@SuppressWarnings("MissingPermissionAnnotation")
public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "ActionReplacingCallback";
@@ -97,6 +97,7 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
@@ -114,6 +115,7 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mLock) {
@@ -132,6 +134,7 @@
}
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId)
throws RemoteException {
@@ -163,6 +166,7 @@
}
@Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId)
throws RemoteException {
// There's no reason to use this class when performing actions. Do something reasonable.
@@ -170,6 +174,7 @@
}
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
throws RemoteException {
mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
@@ -285,6 +290,7 @@
}
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId)
throws RemoteException {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index d279bd5..2f54f8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -240,7 +240,7 @@
}
private void clear(MotionEvent event, int policyFlags) {
- if (mState.isTouchExploring()) {
+ if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
// If a touch exploration gesture is in progress send events for its end.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
@@ -563,7 +563,7 @@
mSendHoverEnterAndMoveDelayed.clear();
mSendHoverExitDelayed.cancel();
// If a touch exploration gesture is in progress send events for its end.
- if (mState.isTouchExploring()) {
+ if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
if (mState.isClear()) {
@@ -1602,6 +1602,9 @@
if (mEvents.size() == 0) {
return;
}
+ if (Flags.sendHoverEventsBasedOnEventStream()) {
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
+ }
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a5bbc7e..aa0af7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
+ @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -682,6 +683,12 @@
if (!mRegistered) {
return false;
}
+ // If the border implementation is on system ui side but the connection is not
+ // established, the fullscreen magnification should not work.
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && !mMagnificationConnectionStateSupplier.get()) {
+ return false;
+ }
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
@@ -941,7 +948,8 @@
@NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
this(
new ControllerContext(
context,
@@ -955,7 +963,8 @@
/* thumbnailSupplier= */ null,
backgroundExecutor,
() -> new Scroller(context),
- TimeAnimator::new);
+ TimeAnimator::new,
+ magnificationConnectionStateSupplier);
}
/** Constructor for tests */
@@ -968,11 +977,13 @@
Supplier<MagnificationThumbnail> thumbnailSupplier,
@NonNull Executor backgroundExecutor,
Supplier<Scroller> scrollerSupplier,
- Supplier<TimeAnimator> timeAnimatorSupplier) {
+ Supplier<TimeAnimator> timeAnimatorSupplier,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
mControllerCtx = ctx;
mLock = lock;
mScrollerSupplier = scrollerSupplier;
mTimeAnimatorSupplier = timeAnimatorSupplier;
+ mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 20bec59..76367a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -798,7 +798,9 @@
mLock,
this,
mScaleProvider,
- mBackgroundExecutor
+ mBackgroundExecutor,
+ () -> (isMagnificationConnectionManagerInitialized()
+ && getMagnificationConnectionManager().isConnected())
);
}
}
@@ -831,6 +833,12 @@
}
}
+ private boolean isMagnificationConnectionManagerInitialized() {
+ synchronized (mLock) {
+ return mMagnificationConnectionManager != null;
+ }
+ }
+
private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
if (mMagnificationConnectionManager == null
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 70ecc05..590a1ef 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -43,8 +43,15 @@
}
flag {
- name: "include_last_focused_id_and_session_id_in_client_state"
+ name: "add_last_focused_id_to_client_state"
namespace: "autofill"
- description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+ description: "Include the current view id into the FillEventHistory events as part of ClientState"
bug: "334141398"
}
+
+flag {
+ name: "add_session_id_to_client_state"
+ namespace: "autofill"
+ description: "Include the session id into the FillEventHistory events as part of ClientState"
+ bug: "333927465"
+}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 7ceb3bb..c96688c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -28,7 +28,6 @@
import android.content.Intent;
import android.content.IntentSender;
import android.os.Handler;
-import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
@@ -43,6 +42,7 @@
import android.service.autofill.SaveRequest;
import android.text.format.DateUtils;
import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.ServiceConnector;
@@ -283,7 +283,8 @@
return callback;
}
- public void onFillCredentialRequest(@NonNull FillRequest request, IBinder autofillCallback) {
+ public void onFillCredentialRequest(@NonNull FillRequest request,
+ IAutoFillManagerClient autofillCallback) {
if (sVerbose) {
Slog.v(TAG, "onFillRequest:" + request);
}
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 044a064..ce9d180 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -21,11 +21,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.os.IBinder;
import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -105,7 +105,8 @@
/**
* Requests a new fill response.
*/
- public void onFillRequest(FillRequest pendingFillRequest, int flag, IBinder client) {
+ public void onFillRequest(FillRequest pendingFillRequest, int flag,
+ IAutoFillManagerClient client) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cd1ef88..3a38406 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -757,14 +757,13 @@
mPendingInlineSuggestionsRequest, id);
}
mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
- mPendingFillRequest.getFlags(), mClient.asBinder());
+ mPendingFillRequest.getFlags(), mClient);
} else if (mRemoteFillService != null) {
if (mIsPrimaryCredential) {
mPendingFillRequest = addCredentialManagerDataToClientState(
mPendingFillRequest,
mPendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(mPendingFillRequest,
- mClient.asBinder());
+ mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
} else {
mRemoteFillService.onFillRequest(mPendingFillRequest);
}
@@ -2898,7 +2897,7 @@
+ ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
}
if (Flags.autofillCredmanDevIntegration() && exception != null
- && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) {
+ && exception instanceof GetCredentialException) {
if (dataset != null && dataset.getFieldIds().size() == 1) {
if (sDebug) {
Slog.d(TAG, "setAuthenticationResultLocked(): result returns with"
@@ -6495,15 +6494,21 @@
}
}
if (exception != null) {
- if (viewId.isVirtualInt()) {
- sendResponseToViewNode(viewId, /*response=*/ null, exception);
- } else {
- mClient.onGetCredentialException(id, viewId, exception.getType(),
- exception.getMessage());
- }
+ mClient.onGetCredentialException(id, viewId, exception.getType(),
+ exception.getMessage());
} else if (response != null) {
if (viewId.isVirtualInt()) {
- sendResponseToViewNode(viewId, response, /*exception=*/ null);
+ ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+ if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
+ Bundle resultData = new Bundle();
+ resultData.putParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ response);
+ viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ resultData);
+ } else {
+ Slog.w(TAG, "View node not found after GetCredentialResponse");
+ }
} else {
mClient.onGetCredentialResponse(id, viewId, response);
}
@@ -6517,30 +6522,6 @@
}
}
- @GuardedBy("mLock")
- private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response,
- GetCredentialException exception) {
- ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
- if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
- Bundle resultData = new Bundle();
- if (response != null) {
- resultData.putParcelable(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
- response);
- viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
- resultData);
- } else if (exception != null) {
- resultData.putStringArray(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
- new String[] {exception.getType(), exception.getMessage()});
- viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR,
- resultData);
- }
- } else {
- Slog.w(TAG, "View node not found after GetCredentialResponse");
- }
- }
-
void autoFillApp(Dataset dataset) {
synchronized (mLock) {
if (mDestroyed) {
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 82e9a26..7a2106b 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -175,7 +175,7 @@
// Create a new association reassigned to this user and a valid association ID
final String packageName = restored.getPackageName();
- final int newId = mAssociationStore.getNextId(userId);
+ final int newId = mAssociationStore.getNextId();
AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
restored).build();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9cfb535..c892b84 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -85,7 +85,7 @@
final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getActiveAssociationsByUser(userId);
- final int maxId = mAssociationStore.getMaxId(userId);
+ final int maxId = mAssociationStore.getMaxId();
out.println("Max ID: " + maxId);
out.println("Association ID | Package Name | Mac Address");
for (AssociationInfo association : associationsForUser) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a18776e..d09d7e6 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -130,7 +130,7 @@
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull AssociationStore mAssociationStore;
@NonNull
- private final ComponentName mCompanionDeviceActivity;
+ private final ComponentName mCompanionAssociationActivity;
public AssociationRequestsProcessor(@NonNull Context context,
@NonNull PackageManagerInternal packageManagerInternal,
@@ -138,9 +138,9 @@
mContext = context;
mPackageManagerInternal = packageManagerInternal;
mAssociationStore = associationStore;
- mCompanionDeviceActivity = createRelative(
+ mCompanionAssociationActivity = createRelative(
mContext.getString(R.string.config_companionDeviceManagerPackage),
- ".CompanionDeviceActivity");
+ ".CompanionAssociationActivity");
}
/**
@@ -204,7 +204,7 @@
extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
final Intent intent = new Intent();
- intent.setComponent(mCompanionDeviceActivity);
+ intent.setComponent(mCompanionAssociationActivity);
intent.putExtras(extras);
// 2b.3. Create a PendingIntent.
@@ -232,7 +232,7 @@
extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
final Intent intent = new Intent();
- intent.setComponent(mCompanionDeviceActivity);
+ intent.setComponent(mCompanionAssociationActivity);
intent.putExtras(extras);
return createPendingIntent(packageUid, intent);
@@ -284,7 +284,7 @@
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
- final int id = mAssociationStore.getNextId(userId);
+ final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b708..29e8095 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -132,7 +132,7 @@
@GuardedBy("mLock")
private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
+ private int mMaxId = 0;
@GuardedBy("mLocalListeners")
private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
@@ -162,7 +162,7 @@
mPersisted = false;
mIdToAssociationMap.clear();
- mUserToMaxId.clear();
+ mMaxId = 0;
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
@@ -172,7 +172,7 @@
for (AssociationInfo association : entry.getValue().getAssociations()) {
mIdToAssociationMap.put(association.getId(), association);
}
- mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
+ mMaxId = Math.max(mMaxId, entry.getValue().getMaxId());
}
mPersisted = true;
@@ -183,18 +183,18 @@
/**
* Get the current max association id.
*/
- public int getMaxId(int userId) {
+ public int getMaxId() {
synchronized (mLock) {
- return mUserToMaxId.getOrDefault(userId, 0);
+ return mMaxId;
}
}
/**
* Get the next available association id.
*/
- public int getNextId(int userId) {
+ public int getNextId() {
synchronized (mLock) {
- return getMaxId(userId) + 1;
+ return getMaxId() + 1;
}
}
@@ -214,7 +214,7 @@
}
mIdToAssociationMap.put(id, association);
- mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
+ mMaxId = Math.max(mMaxId, id);
writeCacheToDisk(userId);
@@ -305,7 +305,7 @@
mExecutor.execute(() -> {
Associations associations = new Associations();
synchronized (mLock) {
- associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
+ associations.setMaxId(mMaxId);
associations.setAssociations(
CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
a -> a.getUserId() == userId));
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 392c0c7..e095fa3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -145,6 +145,7 @@
defaults: [
"platform_service_defaults",
"android.hardware.power-java_shared",
+ "latest_android_hardware_broadcastradio_java_static",
],
srcs: [
":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -207,7 +208,6 @@
"android.hardware.boot-V1.2-java", // HIDL
"android.hardware.boot-V1-java", // AIDL
"android.hardware.broadcastradio-V2.0-java", // HIDL
- "android.hardware.broadcastradio-V2-java", // AIDL
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
@@ -243,7 +243,6 @@
"com.android.sysprop.watchdog",
"securebox",
"apache-commons-math",
- "backstage_power_flags_lib",
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
diff --git a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
rename to services/core/java/com/android/server/ExplicitHealthCheckController.java
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index bdc4a7a..2545620 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@
per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
+per-file ExplicitHealthCheckController.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
@@ -35,9 +36,9 @@
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
-per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
+per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS
-per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com
+per-file RescueParty.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file SensitiveContentProtectionManagerService.java = file:/core/java/android/permission/OWNERS
per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
rename to services/core/java/com/android/server/PackageWatchdog.java
index 75a8bdf..6f20adf 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
-import android.utils.BackgroundThread;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/RescueParty.java
rename to services/core/java/com/android/server/RescueParty.java
index f86eb61..271d552 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@
import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -43,11 +44,10 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import android.utils.ArrayUtils;
-import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 1e7bc39..6c7546e 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.permission.flags.Flags.sensitiveContentImprovements;
import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
@@ -24,6 +25,7 @@
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -91,9 +93,11 @@
private boolean mProjectionActive = false;
private static class MediaProjectionSession {
- final int mUid;
- final long mSessionId;
- final boolean mIsExempted;
+ private final int mUid;
+ private final long mSessionId;
+ private final boolean mIsExempted;
+ private final ArraySet<String> mAllSeenNotificationKeys = new ArraySet<>();
+ private final ArraySet<String> mSeenOtpNotificationKeys = new ArraySet<>();
MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
mUid = uid;
@@ -123,6 +127,14 @@
);
}
+ public void logAppNotificationsProtected() {
+ FrameworkStatsLog.write(
+ SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION,
+ mSessionId,
+ mAllSeenNotificationKeys.size(),
+ mSeenOtpNotificationKeys.size());
+ }
+
public void logAppBlocked(int uid) {
FrameworkStatsLog.write(
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
@@ -142,6 +154,32 @@
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
);
}
+
+ private void addSeenNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ }
+
+ private void addSeenOtpNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ mSeenOtpNotificationKeys.add(key);
+ }
+
+ public void addSeenNotifications(
+ @NonNull StatusBarNotification[] notifications,
+ @NonNull RankingMap rankingMap) {
+ for (StatusBarNotification sbn : notifications) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to parse null notification");
+ continue;
+ }
+
+ if (notificationHasSensitiveContent(sbn, rankingMap)) {
+ addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ addSeenNotificationKey(sbn.getKey());
+ }
+ }
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -297,6 +335,9 @@
mProjectionActive = false;
if (mMediaProjectionSession != null) {
mMediaProjectionSession.logProjectionSessionStop();
+ if (sensitiveContentImprovements()) {
+ mMediaProjectionSession.logAppNotificationsProtected();
+ }
mMediaProjectionSession = null;
}
@@ -334,9 +375,14 @@
notifications = new StatusBarNotification[0];
}
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ mMediaProjectionSession.addSeenNotifications(notifications, rankingMap);
+ }
+
// notify windowmanager of any currently posted sensitive content notifications
ArraySet<PackageInfo> packageInfos =
getSensitivePackagesFromNotifications(notifications, rankingMap);
+
if (packageInfos.size() > 0) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
}
@@ -420,6 +466,14 @@
mWindowManager.addBlockScreenCaptureForApps(
new ArraySet(Set.of(packageInfo)));
}
+
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ if (packageInfo != null) {
+ mMediaProjectionSession.addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ mMediaProjectionSession.addSeenNotificationKey(sbn.getKey());
+ }
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 67e18ca..4dd3a8f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -149,7 +149,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
@@ -3278,7 +3277,7 @@
throws RemoteException {
super.setCeStorageProtection_enforcePermission();
- mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
+ mVold.setCeStorageProtection(userId, secret);
}
/* Only for use by LockSettingsService */
@@ -3288,7 +3287,7 @@
super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, secret);
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ea82b0..26e9bf5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -240,6 +240,7 @@
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.ServiceRecord.ShortFgsInfo;
+import com.android.server.am.ServiceRecord.TimeLimitedFgsInfo;
import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
import com.android.server.utils.AnrTimer;
@@ -500,6 +501,12 @@
// see ServiceRecord#getEarliestStopTypeAndTime()
private final ServiceAnrTimer mFGSAnrTimer;
+ /**
+ * Mapping of uid to {fgs_type, fgs_info} for time limited fgs types such as dataSync and
+ * mediaProcessing.
+ */
+ final SparseArray<SparseArray<TimeLimitedFgsInfo>> mTimeLimitedFgsInfo = new SparseArray<>();
+
// allowlisted packageName.
ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -2275,12 +2282,12 @@
// Whether to extend the SHORT_SERVICE time out.
boolean extendShortServiceTimeout = false;
- // Whether to extend the timeout for a time-limited FGS type.
- boolean extendFgsTimeout = false;
// Whether setFgsRestrictionLocked() is called in here. Only used for logging.
boolean fgsRestrictionRecalculated = false;
+ final int previousFgsType = r.foregroundServiceType;
+
int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
@@ -2321,19 +2328,6 @@
final boolean isOldTypeShortFgsAndTimedOut =
r.shouldTriggerShortFgsTimeout(nowUptime);
- // Calling startForeground on a FGS type which has a time limit will only be
- // allowed if the app is in a state where it can normally start another FGS.
- // The timeout will behave as follows:
- // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE>
- // - If the start succeeds, the timeout is reset.
- // B) <TIME_LIMITED_TYPE> -> non-time-limited type
- // - If the start succeeds, the timeout will stop.
- // C) non-time-limited type -> <TIME_LIMITED_TYPE>
- // - If the start succeeds, the timeout will start.
- final boolean isOldTypeTimeLimited = r.isFgsTimeLimited();
- final boolean isNewTypeTimeLimited =
- r.canFgsTypeTimeOut(foregroundServiceType);
-
// If true, we skip the BFSL check.
boolean bypassBfslCheck = false;
@@ -2402,7 +2396,11 @@
// "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
}
}
- } else if (r.isForeground && isOldTypeTimeLimited) {
+ } else if (getTimeLimitedFgsType(foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ // Calling startForeground on a FGS type which has a time limit will only be
+ // allowed if the app is in a state where it can normally start another FGS
+ // and it hasn't hit the time limit for that type in the past 24hrs.
// See if the app could start an FGS or not.
r.clearFgsAllowStart();
@@ -2413,20 +2411,37 @@
final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService
|| r.isFgsAllowedStart();
-
if (fgsStartAllowed) {
- if (isNewTypeTimeLimited) {
- // Note: in the future, we may want to look into metrics to see if
- // apps are constantly switching between a time-limited type and a
- // non-time-limited type or constantly calling startForeground()
- // opportunistically on the same type to gain runtime and apply the
- // stricter timeout. For now, always extend the timeout if the app
- // is in a state where it's allowed to start a FGS.
- extendFgsTimeout = true;
- } else {
- // FGS type is changing from a time-restricted type to one without
- // a time limit so proceed as normal.
- // The timeout will stop later, in maybeUpdateFgsTrackingLocked().
+ SparseArray<TimeLimitedFgsInfo> fgsInfo =
+ mTimeLimitedFgsInfo.get(r.appInfo.uid);
+ if (fgsInfo == null) {
+ fgsInfo = new SparseArray<>();
+ mTimeLimitedFgsInfo.put(r.appInfo.uid, fgsInfo);
+ }
+ final int timeLimitedFgsType =
+ getTimeLimitedFgsType(foregroundServiceType);
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
+ if (fgsTypeInfo != null) {
+ // TODO(b/330399444): check to see if all time book-keeping for
+ // time limited types should use elapsedRealtime instead of uptime
+ final long before24Hr = Math.max(0,
+ SystemClock.elapsedRealtime() - (24 * 60 * 60 * 1000));
+ final long lastTimeOutAt = fgsTypeInfo.getTimeLimitExceededAt();
+ if (fgsTypeInfo.getFirstFgsStartTime() < before24Hr
+ || (lastTimeOutAt != Long.MIN_VALUE
+ && r.app.mState.getLastTopTime() > lastTimeOutAt)) {
+ // Reset the time limit info for this fgs type if it has been
+ // more than 24hrs since the first fgs start or if the app was
+ // in the TOP state after time limit was exhausted.
+ fgsTypeInfo.reset();
+ } else if (lastTimeOutAt > 0) {
+ // Time limit was exhausted within the past 24 hours and the app
+ // has not been in the TOP state since then, throw an exception.
+ throw new ForegroundServiceStartNotAllowedException("Time limit"
+ + " already exhausted for foreground service type "
+ + ServiceInfo.foregroundServiceTypeToLabel(
+ foregroundServiceType));
+ }
}
} else {
// This case will be handled in the BFSL check below.
@@ -2673,7 +2688,7 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
+ maybeUpdateFgsTrackingLocked(r, previousFgsType);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -3687,75 +3702,117 @@
}
}
- void onFgsTimeout(ServiceRecord sr) {
- synchronized (mAm) {
- final long nowUptime = SystemClock.uptimeMillis();
- final int fgsType = sr.getTimedOutFgsType(nowUptime);
- if (fgsType == -1) {
- mFGSAnrTimer.discard(sr);
- return;
- }
- Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
- + ") timed out: " + sr);
- mFGSAnrTimer.accept(sr);
- traceInstant("FGS timed out: ", sr);
-
- logFGSStateChangeLocked(sr,
- FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
- nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
- FGS_STOP_REASON_UNKNOWN,
- FGS_TYPE_POLICY_CHECK_UNKNOWN,
- FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
- false /* fgsRestrictionRecalculated */
- );
- try {
- sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
- } catch (RemoteException e) {
- Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
- }
-
- // ANR the service after giving the service some time to clean up.
- // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that
- // is not "now". Compute the time from "now" when starting the anr timer.
- final long anrTime = sr.getEarliestStopTypeAndTime().second
- + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis();
- mFGSAnrTimer.start(sr, anrTime);
+ /**
+ * @return the fgs type for this service which has the most lenient time limit; if none of the
+ * types are time-restricted, return {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ */
+ @ServiceInfo.ForegroundServiceType int getTimeLimitedFgsType(int foregroundServiceType) {
+ int fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+ long timeout = 0;
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
+ timeout = mAm.mConstants.mMediaProcessingFgsTimeoutDuration;
}
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+ // update the timeout and type if this type has a more lenient time limit
+ if (timeout == 0 || mAm.mConstants.mDataSyncFgsTimeoutDuration > timeout) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+ timeout = mAm.mConstants.mDataSyncFgsTimeoutDuration;
+ }
+ }
+ // Add logic for time limits introduced in the future for other fgs types above.
+ return fgsType;
}
- private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) {
- if (!sr.isFgsTimeLimited()) {
- // Reset timers if they exist.
- sr.setIsFgsTimeLimited(false);
- mFGSAnrTimer.cancel(sr);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, int previousFgsType) {
+ final int previouslyTimeLimitedType = getTimeLimitedFgsType(previousFgsType);
+ if (previouslyTimeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
+ && !sr.isFgsTimeLimited()) {
+ // FGS was not previously time-limited and new type isn't either.
return;
}
- if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) {
- traceInstant("FGS start: ", sr);
- sr.setIsFgsTimeLimited(true);
+ if (previouslyTimeLimitedType != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ // FGS is switching types and the previous type was time-limited so update the runtime.
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(previouslyTimeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime();
+ // TODO(b/330399444): handle the case where an app is running 2 services of the
+ // same time-limited type in parallel and stops one of them which leads to the
+ // second running one gaining additional runtime.
+ }
+ }
- // We'll restart the timeout.
- mFGSAnrTimer.cancel(sr);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
-
- final Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
- mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second);
+ if (!sr.isFgsTimeLimited()) {
+ // Reset timers since new type does not have a timeout.
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ return;
+ }
}
+
+ traceInstant("FGS start: ", sr);
+ final long nowUptime = SystemClock.uptimeMillis();
+
+ // Fetch/create/update the fgs info for the time-limited type.
+ SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo == null) {
+ fgsInfo = new SparseArray<>();
+ mTimeLimitedFgsInfo.put(sr.appInfo.uid, fgsInfo);
+ }
+ final int timeLimitedFgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
+ if (fgsTypeInfo == null) {
+ fgsTypeInfo = sr.createTimeLimitedFgsInfo(nowUptime);
+ fgsInfo.put(timeLimitedFgsType, fgsTypeInfo);
+ }
+ fgsTypeInfo.setLastFgsStartTime(nowUptime);
+
+ // We'll cancel the previous ANR timer and start a fresh one below.
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ final long timeoutCallbackTime = sr.getNextFgsStopTime(timeLimitedFgsType, fgsTypeInfo);
+ if (timeoutCallbackTime == Long.MAX_VALUE) {
+ // This should never happen since we only get to this point if the service record's
+ // foregroundServiceType attribute contains a type that can be timed-out.
+ Slog.wtf(TAG, "Couldn't calculate timeout for time-limited fgs: " + sr);
+ return;
+ }
+ mAm.mHandler.sendMessageAtTime(msg, timeoutCallbackTime);
}
private void maybeStopFgsTimeoutLocked(ServiceRecord sr) {
- sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state.
- if (!sr.isFgsTimeLimited()) {
- return; // if none of the types are time-limited, return.
+ final int timeLimitedType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (timeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ return; // if the current fgs type is not time-limited, return.
+ }
+
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime();
+ }
}
Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
mFGSAnrTimer.cancel(sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
}
+ void onUidRemovedLocked(int uid) {
+ // Remove all time-limited fgs tracking info stored for this uid.
+ mTimeLimitedFgsInfo.delete(uid);
+ }
+
boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) {
final int userId = UserHandle.getCallingUserId();
final long ident = mAm.mInjector.clearCallingIdentity();
@@ -3764,25 +3821,67 @@
if (sr == null) {
return false;
}
- final long nowUptime = SystemClock.uptimeMillis();
- return sr.getTimedOutFgsType(nowUptime) != -1;
+ return getTimeLimitedFgsType(sr.foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
} finally {
mAm.mInjector.restoreCallingIdentity(ident);
}
}
+ void onFgsTimeout(ServiceRecord sr) {
+ synchronized (mAm) {
+ final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ mFGSAnrTimer.discard(sr);
+ return;
+ }
+ Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ + ") timed out: " + sr);
+ mFGSAnrTimer.accept(sr);
+ traceInstant("FGS timed out: ", sr);
+
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(fgsType);
+ if (fgsTypeInfo != null) {
+ // Update total runtime for the time-limited fgs type and mark it as timed out.
+ final long nowUptime = SystemClock.uptimeMillis();
+ fgsTypeInfo.updateTotalRuntime();
+ fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
+
+ logFGSStateChangeLocked(sr,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+ nowUptime > fgsTypeInfo.getLastFgsStartTime()
+ ? (int) (nowUptime - fgsTypeInfo.getLastFgsStartTime()) : 0,
+ FGS_STOP_REASON_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+ false /* fgsRestrictionRecalculated */
+ );
+ }
+ }
+
+ try {
+ sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
+ } catch (RemoteException e) {
+ Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
+ }
+
+ // ANR the service after giving the service some time to clean up.
+ mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
+ }
+ }
+
void onFgsAnrTimeout(ServiceRecord sr) {
- final long nowUptime = SystemClock.uptimeMillis();
- final int fgsType = sr.getTimedOutFgsType(nowUptime);
- if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) {
- return; // no timed out FGS type was found
+ final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ return; // no timed out FGS type was found (either it was stopped or it switched types)
}
final String reason = "A foreground service of type "
+ ServiceInfo.foregroundServiceTypeToLabel(fgsType)
- + " did not stop within a timeout: " + sr.getComponentName();
+ + " did not stop within its timeout: " + sr.getComponentName();
final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
-
tr.mLatencyTracker.waitingOnAMSLockStarted();
synchronized (mAm) {
tr.mLatencyTracker.waitingOnAMSLockEnded();
@@ -5725,14 +5824,11 @@
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- true);
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app,
+ r);
if (isolated) {
r.isolationHostProc = app;
}
- } else {
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9401a56..6612319 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,6 +37,9 @@
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -685,8 +688,6 @@
public final IntentFirewall mIntentFirewall;
- public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
-
/**
* The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
*/
@@ -2598,7 +2599,6 @@
BackgroundThread.getHandler(), this);
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
- mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2847,13 +2847,12 @@
updateCpuStatsNow();
synchronized (mProcLock) {
mOnBattery = DEBUG_POWER ? true : onBattery;
- mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
- mOomAdjProfiler.reset();
+ // Empty for now.
}
@Override
@@ -5109,10 +5108,19 @@
} // else, stopped packages in private space may still hit the logic below
}
}
+
+ final boolean wasForceStopped = app.wasForceStopped()
+ || app.getWindowProcessController().wasForceStopped();
+ if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
+ noteAppRestrictionEnabled(app.info.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_USAGE, "unknown", 0L);
+ }
+
if (!sendBroadcast) {
if (!android.content.pm.Flags.stayStopped()) return;
// Nothing to do if it wasn't previously stopped
- if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ if (!wasForceStopped) {
return;
}
}
@@ -7419,7 +7427,6 @@
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
@@ -8951,8 +8958,10 @@
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
+ final int backgroundUserScheduledStopTimeSecs = res.getInteger(
+ com.android.internal.R.integer.config_backgroundUserScheduledStopTimeSecs);
mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
- delayUserDataLocking);
+ delayUserDataLocking, backgroundUserScheduledStopTimeSecs);
}
mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
@@ -10549,12 +10558,6 @@
pw.println(
"-------------------------------------------------------------------------------");
}
- mOomAdjProfiler.dump(pw);
- pw.println();
- if (dumpAll) {
- pw.println(
- "-------------------------------------------------------------------------------");
- }
dumpLmkLocked(pw);
}
pw.println();
@@ -14338,6 +14341,20 @@
int newBackupUid;
synchronized(this) {
+ if (android.app.Flags.appRestrictionsApi()) {
+ try {
+ final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName,
+ UserHandle.getUserId(app.uid));
+ if (wasStopped) {
+ noteAppRestrictionEnabled(app.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_DEFAULT, "restore", 0L);
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "No such package", e);
+ }
+ }
+
// !!! TODO: currently no check here that we're already bound
// Backup agent is now in use, its package can't be stopped.
try {
@@ -15485,6 +15502,7 @@
intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
} else {
mAppOpsService.uidRemoved(uid);
+ mServices.onUidRemovedLocked(uid);
}
}
break;
@@ -20123,6 +20141,34 @@
}
/**
+ * Log the reason for changing an app restriction. Purely used for logging purposes and does not
+ * cause any change to app state.
+ *
+ * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+ */
+ @Override
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+ if (!android.app.Flags.appRestrictionsApi()) return;
+
+ enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+ "noteAppRestrictionEnabled()");
+
+ final int userId = UserHandle.getCallingUserId();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ if (uid == -1) {
+ uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ }
+ mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
+ enabled, reason, subReason, threshold);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Get an app's background restriction level.
* This interface is intended for the shell command to use.
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 372ec45..bf4f34f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -174,6 +174,8 @@
private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss", Locale.ROOT);
+ private static final String PROFILER_OUTPUT_VERSION_FLAG = "--profiler-output-version";
+
// IPC interface to activity manager -- don't need to do additional security checks.
final IActivityManager mInterface;
final IActivityTaskManager mTaskInterface;
@@ -199,6 +201,7 @@
private String mAgent; // Agent to attach on startup.
private boolean mAttachAgentDuringBind; // Whether agent should be attached late.
private int mClockType; // Whether we need thread cpu / wall clock / both.
+ private int mProfilerOutputVersion; // The version of the profiler output.
private int mDisplayId;
private int mTaskDisplayAreaFeatureId;
private int mWindowingMode;
@@ -527,6 +530,8 @@
} else if (opt.equals("--clock-type")) {
String clock_type = getNextArgRequired();
mClockType = ProfilerInfo.getClockTypeFromString(clock_type);
+ } else if (opt.equals(PROFILER_OUTPUT_VERSION_FLAG)) {
+ mProfilerOutputVersion = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--streaming")) {
mStreaming = true;
} else if (opt.equals("--attach-agent")) {
@@ -579,7 +584,7 @@
} else if (opt.equals("--splashscreen-show-icon")) {
mShowSplashScreen = true;
} else if (opt.equals("--dismiss-keyguard-if-insecure")
- || opt.equals("--dismiss-keyguard")) {
+ || opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
} else if (opt.equals("--allow-fgs-start-reason")) {
final int reasonCode = Integer.parseInt(getNextArgRequired());
@@ -692,8 +697,9 @@
return 1;
}
}
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
- mStreaming, mAgent, mAttachAgentDuringBind, mClockType);
+ profilerInfo =
+ new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, mStreaming,
+ mAgent, mAttachAgentDuringBind, mClockType, mProfilerOutputVersion);
}
pw.println("Starting: " + intent);
@@ -1036,6 +1042,7 @@
mSamplingInterval = 0;
mStreaming = false;
mClockType = ProfilerInfo.CLOCK_TYPE_DEFAULT;
+ mProfilerOutputVersion = ProfilerInfo.OUTPUT_VERSION_DEFAULT;
String process = null;
@@ -1050,6 +1057,8 @@
} else if (opt.equals("--clock-type")) {
String clock_type = getNextArgRequired();
mClockType = ProfilerInfo.getClockTypeFromString(clock_type);
+ } else if (opt.equals(PROFILER_OUTPUT_VERSION_FLAG)) {
+ mProfilerOutputVersion = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--streaming")) {
mStreaming = true;
} else if (opt.equals("--sampling")) {
@@ -1097,7 +1106,7 @@
return -1;
}
profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming,
- null, false, mClockType);
+ null, false, mClockType, mProfilerOutputVersion);
}
if (!mInterface.profileControl(process, userId, start, profilerInfo, profileType)) {
@@ -4006,8 +4015,12 @@
return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
case "background_restricted":
return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- case "hibernation":
- return ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+ case "force_stopped":
+ return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+ case "user_launch_only":
+ return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+ case "custom":
+ return ActivityManager.RESTRICTION_LEVEL_CUSTOM;
default:
return ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
}
@@ -4192,6 +4205,7 @@
pw.println(" Print this help text.");
pw.println(" start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");
pw.println(" [--sampling INTERVAL] [--clock-type <TYPE>] [--streaming]");
+ pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " NUMBER]");
pw.println(" [-R COUNT] [-S] [--track-allocation]");
pw.println(" [--user <USER_ID> | current] [--suspend] <INTENT>");
pw.println(" Start an Activity. Options are:");
@@ -4207,6 +4221,8 @@
pw.println(" The default value is dual. (use with --start-profiler)");
pw.println(" --streaming: stream the profiling output to the specified file");
pw.println(" (use with --start-profiler)");
+ pw.println(" " + PROFILER_OUTPUT_VERSION_FLAG + " Specify the version of the");
+ pw.println(" profiling output (use with --start-profiler)");
pw.println(" -P <FILE>: like above, but profiling stops when app goes idle");
pw.println(" --attach-agent <agent>: attach the given agent before binding");
pw.println(" --attach-agent-bind <agent>: attach the given agent during binding");
@@ -4298,6 +4314,7 @@
pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
pw.println(" profile start [--user <USER_ID> current]");
pw.println(" [--clock-type <TYPE>]");
+ pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " VERSION]");
pw.println(" [--sampling INTERVAL | --streaming] <PROCESS> <FILE>");
pw.println(" Start profiler on a process. The given <PROCESS> argument");
pw.println(" may be either a process name or pid. Options are:");
@@ -4307,6 +4324,8 @@
pw.println(" --clock-type <TYPE>: use the specified clock to report timestamps.");
pw.println(" The type can be one of wall | thread-cpu | dual. The default");
pw.println(" value is dual.");
+ pw.println(" " + PROFILER_OUTPUT_VERSION_FLAG + "VERSION: specifies the output");
+ pw.println(" format version");
pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
pw.println(" between samples.");
pw.println(" --streaming: stream the profiling output to the specified file.");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 6c16fba0..dda48ad 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -2414,8 +2414,8 @@
}
}
} else if (instr != null && instr.mProfileFile != null) {
- profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false,
- null, false, 0);
+ profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, null,
+ false, 0, ProfilerInfo.OUTPUT_VERSION_DEFAULT);
}
if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) {
// We need to do a debuggable check here. See setAgentApp for why the check is
@@ -2425,7 +2425,8 @@
// Do not overwrite already requested agent.
if (profilerInfo == null) {
profilerInfo = new ProfilerInfo(null, null, 0, false, false,
- mAppAgentMap.get(processName), true, 0);
+ mAppAgentMap.get(processName), true, 0,
+ ProfilerInfo.OUTPUT_VERSION_DEFAULT);
} else if (profilerInfo.agent == null) {
profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true);
}
@@ -2552,14 +2553,16 @@
if (mProfileData.getProfilerInfo() != null) {
pw.println(" mProfileFile=" + mProfileData.getProfilerInfo().profileFile
+ " mProfileFd=" + mProfileData.getProfilerInfo().profileFd);
- pw.println(" mSamplingInterval="
- + mProfileData.getProfilerInfo().samplingInterval
+ pw.println(
+ " mSamplingInterval=" + mProfileData.getProfilerInfo().samplingInterval
+ " mAutoStopProfiler="
+ mProfileData.getProfilerInfo().autoStopProfiler
+ " mStreamingOutput="
+ mProfileData.getProfilerInfo().streamingOutput
+ " mClockType="
- + mProfileData.getProfilerInfo().clockType);
+ + mProfileData.getProfilerInfo().clockType
+ + " mProfilerOutputVersion="
+ + mProfileData.getProfilerInfo().profilerOutputVersion);
pw.println(" mProfileType=" + mProfileType);
}
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 117221f..c5cad14 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -23,11 +23,20 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
-import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
+import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
+import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -93,6 +102,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManager.RestrictionReason;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
@@ -344,6 +354,7 @@
static final int TRACKER_TYPE_PERMISSION = 5;
static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+
@IntDef(prefix = { "TRACKER_TYPE_" }, value = {
TRACKER_TYPE_UNKNOWN,
TRACKER_TYPE_BATTERY,
@@ -1714,7 +1725,7 @@
String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
boolean allowRequestBgRestricted, boolean calcTrackers) {
if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
- return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
+ return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo);
}
@RestrictionLevel int level;
TrackerInfo trackerInfo = null;
@@ -2034,7 +2045,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case RESTRICTION_LEVEL_HIBERNATION:
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
@@ -2354,6 +2365,85 @@
}
}
+ /**
+ * Log a change in restriction state with a reason and threshold.
+ * @param packageName
+ * @param uid
+ * @param restrictionType
+ * @param enabled
+ * @param reason
+ * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads
+ * Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH
+ * @param threshold
+ */
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @RestrictionReason int reason, String subReason, long threshold) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+ + restrictionType + " reason=" + reason + ", subReason=" + subReason
+ + ", threshold=" + threshold);
+ }
+
+ // Limit the length of the free-form subReason string
+ if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
+ Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
+ }
+
+ // Log the restriction reason
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid,
+ getRestrictionTypeStatsd(restrictionType),
+ enabled,
+ getRestrictionChangeReasonStatsd(reason, subReason),
+ subReason,
+ threshold);
+ }
+
+ private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
+ return switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN;
+ case RESTRICTION_LEVEL_UNRESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED;
+ case RESTRICTION_LEVEL_EXEMPTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED;
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE;
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET;
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED;
+ case RESTRICTION_LEVEL_FORCE_STOPPED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED;
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM;
+ };
+ }
+
+ private int getRestrictionChangeReasonStatsd(int reason, String subReason) {
+ return switch (reason) {
+ case RESTRICTION_REASON_DEFAULT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT;
+ case RESTRICTION_REASON_DORMANT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT;
+ case RESTRICTION_REASON_USAGE ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
+ case RESTRICTION_REASON_USER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
+ case RESTRICTION_REASON_USER_NUDGED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
+ case RESTRICTION_REASON_SYSTEM_HEALTH ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
+ case RESTRICTION_REASON_REMOTE_TRIGGER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
+ };
+ }
+
static class NotificationHelper {
static final String PACKAGE_SCHEME = "package";
static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 0728ea8..2be1fe2 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -196,6 +196,8 @@
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+
+ // TODO: handle possible alarm activity start.
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
@@ -313,7 +315,7 @@
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, boolean cold) {
+ ServiceRecord serviceRecord) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -323,8 +325,9 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+
+ // TODO: handle possible alarm service start.
start.setReason(serviceRecord.permission != null
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
@@ -336,8 +339,9 @@
}
}
- public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
- BroadcastRecord broadcast, boolean cold) {
+ /** Process a broadcast triggered app start. */
+ public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent,
+ boolean isAlarm) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -347,26 +351,19 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
- if (broadcast == null) {
- start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
- } else if (broadcast.alarm) {
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+ if (isAlarm) {
start.setReason(ApplicationStartInfo.START_REASON_ALARM);
- } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
- start.setReason(ApplicationStartInfo.START_REASON_PUSH);
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
- start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
} else {
start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
}
- start.setIntent(broadcast != null ? broadcast.intent : null);
+ start.setIntent(intent);
addStartInfoLocked(start);
}
}
- public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
- boolean cold) {
+ /** Process a content provider triggered app start. */
+ public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -376,8 +373,7 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
addStartInfoLocked(start);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c082889..48dd039 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1030,6 +1030,10 @@
"startProcessLocked failed");
return true;
}
+ // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
+ mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
+ SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
+ r.alarm /* isAlarm */);
return false;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f76bf37..28fd197 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,6 +179,7 @@
final int expectedUserId = userId;
synchronized (mService) {
long startTime = SystemClock.uptimeMillis();
+ long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord r = null;
if (caller != null) {
@@ -585,6 +586,8 @@
callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
firstLaunch,
0L /* TODO: stoppedDuration */);
+ mService.mProcessList.getAppStartInfoTracker()
+ .handleProcessContentProviderStart(startElapsedTimeNs, proc);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
deleted file mode 100644
index 0869114..0000000
--- a/services/core/java/com/android/server/am/OomAdjProfiler.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import android.os.Message;
-import android.os.PowerManagerInternal;
-import android.os.Process;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.ProcessCpuTracker;
-import com.android.internal.util.RingBuffer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-import java.io.PrintWriter;
-
-public class OomAdjProfiler {
- private static final int MSG_UPDATE_CPU_TIME = 42;
-
- @GuardedBy("this")
- private boolean mOnBattery;
- @GuardedBy("this")
- private boolean mScreenOff;
-
- /** The value of {@link #mOnBattery} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledOnBattery;
- /** The value of {@link #mScreenOff} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledScreenOff;
-
- @GuardedBy("this")
- private long mOomAdjStartTimeUs;
- @GuardedBy("this")
- private boolean mOomAdjStarted;
-
- @GuardedBy("this")
- private CpuTimes mOomAdjRunTime = new CpuTimes();
- @GuardedBy("this")
- private CpuTimes mSystemServerCpuTime = new CpuTimes();
-
- @GuardedBy("this")
- private long mLastSystemServerCpuTimeMs;
- @GuardedBy("this")
- private boolean mSystemServerCpuTimeUpdateScheduled;
- private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
-
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
-
- @GuardedBy("this")
- private long mTotalOomAdjRunTimeUs;
- @GuardedBy("this")
- private int mTotalOomAdjCalls;
-
- void batteryPowerChanged(boolean onBattery) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mOnBattery = onBattery;
- }
- }
-
- void onWakefulnessChanged(int wakefulness) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
- }
- }
-
- void oomAdjStarted() {
- synchronized (this) {
- mOomAdjStartTimeUs = SystemClock.currentThreadTimeMicro();
- mOomAdjStarted = true;
- }
- }
-
- void oomAdjEnded() {
- synchronized (this) {
- if (!mOomAdjStarted) {
- return;
- }
- long elapsedUs = SystemClock.currentThreadTimeMicro() - mOomAdjStartTimeUs;
- mOomAdjRunTime.addCpuTimeUs(elapsedUs);
- mTotalOomAdjRunTimeUs += elapsedUs;
- mTotalOomAdjCalls++;
- }
- }
-
- private void scheduleSystemServerCpuTimeUpdate() {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mLastScheduledOnBattery = mOnBattery;
- mLastScheduledScreenOff = mScreenOff;
- mSystemServerCpuTimeUpdateScheduled = true;
- Message scheduledMessage = PooledLambda.obtainMessage(
- OomAdjProfiler::updateSystemServerCpuTime,
- this, mLastScheduledOnBattery, mLastScheduledScreenOff, true);
- scheduledMessage.setWhat(MSG_UPDATE_CPU_TIME);
-
- BackgroundThread.getHandler().sendMessage(scheduledMessage);
- }
- }
-
- private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff,
- boolean onlyIfScheduled) {
- final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
- synchronized (this) {
- if (onlyIfScheduled && !mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mSystemServerCpuTime.addCpuTimeMs(
- cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
- mLastSystemServerCpuTimeMs = cpuTimeMs;
- mSystemServerCpuTimeUpdateScheduled = false;
- }
- }
-
- void reset() {
- synchronized (this) {
- if (mSystemServerCpuTime.isEmpty()) {
- return;
- }
- mOomAdjRunTimesHist.append(mOomAdjRunTime);
- mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
- mOomAdjRunTime = new CpuTimes();
- mSystemServerCpuTime = new CpuTimes();
- }
- }
-
- void dump(PrintWriter pw) {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- // Cancel the scheduled update since we're going to update it here instead.
- BackgroundThread.getHandler().removeMessages(MSG_UPDATE_CPU_TIME);
- // Make sure the values are attributed to the right states.
- updateSystemServerCpuTime(mLastScheduledOnBattery, mLastScheduledScreenOff, false);
- } else {
- updateSystemServerCpuTime(mOnBattery, mScreenOff, false);
- }
-
- pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
- + "(most recent first):");
- if (!mSystemServerCpuTime.isEmpty()) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(mSystemServerCpuTime);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(mOomAdjRunTime);
- }
- final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
- final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
- for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(systemServerCpuTimes[i]);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(oomAdjRunTimes[i]);
- }
- if (mTotalOomAdjCalls != 0) {
- pw.println("System server total oomAdj runtimes (us) since boot:");
- pw.print(" cpu time spent=");
- pw.print(mTotalOomAdjRunTimeUs);
- pw.print(" number of calls=");
- pw.print(mTotalOomAdjCalls);
- pw.print(" average=");
- pw.println(mTotalOomAdjRunTimeUs / mTotalOomAdjCalls);
- }
- }
- }
-
- private class CpuTimes {
- private long mOnBatteryTimeUs;
- private long mOnBatteryScreenOffTimeUs;
-
- public void addCpuTimeMs(long cpuTimeMs) {
- addCpuTimeUs(cpuTimeMs * 1000, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
- addCpuTimeUs(cpuTimeMs * 1000, onBattery, screenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs) {
- addCpuTimeUs(cpuTimeUs, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs, boolean onBattery, boolean screenOff) {
- if (onBattery) {
- mOnBatteryTimeUs += cpuTimeUs;
- if (screenOff) {
- mOnBatteryScreenOffTimeUs += cpuTimeUs;
- }
- }
- }
-
- public boolean isEmpty() {
- return mOnBatteryTimeUs == 0 && mOnBatteryScreenOffTimeUs == 0;
- }
-
- public String toString() {
- return "[" + (mOnBatteryTimeUs / 1000) + ","
- + (mOnBatteryScreenOffTimeUs / 1000) + "]";
- }
- }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ea7a21d..9b72db8 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -600,7 +600,6 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
final ProcessStateRecord state = app.mState;
@@ -630,7 +629,6 @@
}
mTmpProcessList.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return true;
}
@@ -849,7 +847,6 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
final ArrayList<ProcessRecord> processes = mTmpProcessList;
@@ -862,7 +859,6 @@
processes.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -895,7 +891,6 @@
mLastReason = oomAdjReason;
if (startProfiling) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
}
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
@@ -989,7 +984,6 @@
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 00e1482..3268b66 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -744,11 +744,9 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
fullUpdateLSP(oomAdjReason);
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -766,14 +764,12 @@
mLastReason = oomAdjReason;
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
synchronized (mProcLock) {
partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
mPendingProcessSet.clear();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 5834dcd..045d137 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
import android.app.BackgroundStartPrivileges;
import android.app.IApplicationThread;
import android.app.Notification;
@@ -56,7 +57,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
-import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -237,8 +237,6 @@
boolean mFgsNotificationShown;
// Whether FGS package has permissions to show notifications.
boolean mFgsHasNotificationPermission;
- // Whether the FGS contains a type that is time limited.
- private boolean mFgsIsTimeLimited;
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@@ -675,6 +673,62 @@
*/
private ShortFgsInfo mShortFgsInfo;
+ /**
+ * Data container class to help track certain fgs info for time-restricted types.
+ */
+ static class TimeLimitedFgsInfo {
+ @UptimeMillisLong
+ private long mFirstFgsStartTime;
+ @UptimeMillisLong
+ private long mLastFgsStartTime;
+ @UptimeMillisLong
+ private long mTimeLimitExceededAt = Long.MIN_VALUE;
+ private long mTotalRuntime = 0;
+
+ TimeLimitedFgsInfo(@UptimeMillisLong long startTime) {
+ mFirstFgsStartTime = startTime;
+ mLastFgsStartTime = startTime;
+ }
+
+ @UptimeMillisLong
+ public long getFirstFgsStartTime() {
+ return mFirstFgsStartTime;
+ }
+
+ public void setLastFgsStartTime(@UptimeMillisLong long startTime) {
+ mLastFgsStartTime = startTime;
+ }
+
+ @UptimeMillisLong
+ public long getLastFgsStartTime() {
+ return mLastFgsStartTime;
+ }
+
+ public void updateTotalRuntime() {
+ mTotalRuntime += SystemClock.uptimeMillis() - mLastFgsStartTime;
+ }
+
+ public long getTotalRuntime() {
+ return mTotalRuntime;
+ }
+
+ public void setTimeLimitExceededAt(@UptimeMillisLong long timeLimitExceededAt) {
+ mTimeLimitExceededAt = timeLimitExceededAt;
+ }
+
+ @UptimeMillisLong
+ public long getTimeLimitExceededAt() {
+ return mTimeLimitExceededAt;
+ }
+
+ public void reset() {
+ mFirstFgsStartTime = 0;
+ mLastFgsStartTime = 0;
+ mTotalRuntime = 0;
+ mTimeLimitExceededAt = Long.MIN_VALUE;
+ }
+ }
+
void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
final int N = list.size();
for (int i=0; i<N; i++) {
@@ -927,7 +981,6 @@
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
pw.print(" foregroundId="); pw.print(foregroundId);
pw.printf(" types=%08X", foregroundServiceType);
- pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
if (isShortFgs() && mShortFgsInfo != null) {
@@ -1803,80 +1856,41 @@
}
/**
+ * Called when a time-limited FGS starts.
+ */
+ public TimeLimitedFgsInfo createTimeLimitedFgsInfo(long nowUptime) {
+ return new TimeLimitedFgsInfo(nowUptime);
+ }
+
+ /**
* @return true if one of the types of this FGS has a time limit.
*/
public boolean isFgsTimeLimited() {
- return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType);
+ return startRequested
+ && isForeground
+ && ams.mServices.getTimeLimitedFgsType(foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
}
/**
- * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}).
+ * @return the next stop time for the given type, based on how long it has already ran for.
+ * The total runtime is automatically reset 24hrs after the first fgs start of this type
+ * or if the app has recently been in the TOP state when the app calls startForeground().
*/
- public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) {
- this.mFgsIsTimeLimited = fgsIsTimeLimited;
- }
-
- /**
- * @return whether {@link #mFgsIsTimeLimited} was previously set or not.
- */
- public boolean wasFgsPreviouslyTimeLimited() {
- return mFgsIsTimeLimited;
- }
-
- /**
- * @return the FGS type if the service has reached its time limit, otherwise -1.
- */
- public int getTimedOutFgsType(long nowUptime) {
- if (!isAppAlive() || !isFgsTimeLimited()) {
- return -1;
+ long getNextFgsStopTime(int fgsType, TimeLimitedFgsInfo fgsInfo) {
+ final long timeLimit;
+ switch (ams.mServices.getTimeLimitedFgsType(fgsType)) {
+ case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:
+ timeLimit = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
+ break;
+ case ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC:
+ timeLimit = ams.mConstants.mDataSyncFgsTimeoutDuration;
+ break;
+ // Add logic for time limits introduced in the future for other fgs types above.
+ default:
+ return Long.MAX_VALUE;
}
-
- final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime();
- if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) {
- return fgsTypeAndStopTime.first;
- }
- return -1; // no fgs type exceeded time limit
- }
-
- /**
- * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS
- * should be stopped (fgs start time + time limit for most restrictive type)
- */
- Pair<Integer, Long> getEarliestStopTypeAndTime() {
- int fgsType = -1;
- long timeout = 0;
- if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
- fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
- timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
- }
- if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
- // update the timeout and type if this type has a more restrictive time limit
- if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) {
- fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
- timeout = ams.mConstants.mDataSyncFgsTimeoutDuration;
- }
- }
- // Add the logic for time limits introduced in the future for other fgs types here.
- return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout));
- }
-
- /**
- * Check if the given types contain a type which is time restricted.
- */
- boolean canFgsTypeTimeOut(int fgsType) {
- // The below conditionals are not simplified on purpose to help with readability.
- if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
- return true;
- }
- if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
- return true;
- }
- // Additional types which have time limits should be added here in the future.
- return false;
+ return fgsInfo.mLastFgsStartTime + Math.max(0, timeLimit - fgsInfo.mTotalRuntime);
}
private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index dd4cee4..b703076 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -59,6 +59,7 @@
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
@@ -200,6 +201,7 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
+ static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150;
private static final int NO_ARG2 = 0;
@@ -251,6 +253,14 @@
@GuardedBy("mLock")
private int mMaxRunningUsers;
+ /**
+ * Number of seconds of uptime after a full user enters the background before we attempt
+ * to stop it due to inactivity. Set to -1 to disable scheduling stopping background users.
+ *
+ * Typically set by config_backgroundUserScheduledStopTimeSecs.
+ */
+ private int mBackgroundUserScheduledStopTimeSecs = -1;
+
// Lock for internal state.
private final Object mLock = new Object();
@@ -453,11 +463,12 @@
}
void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
- boolean delayUserDataLocking) {
+ boolean delayUserDataLocking, int backgroundUserScheduledStopTimeSecs) {
synchronized (mLock) {
mUserSwitchUiEnabled = userSwitchUiEnabled;
mMaxRunningUsers = maxRunningUsers;
mDelayUserDataLocking = delayUserDataLocking;
+ mBackgroundUserScheduledStopTimeSecs = backgroundUserScheduledStopTimeSecs;
mInitialized = true;
}
}
@@ -1091,6 +1102,10 @@
final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
final UserState uss = mStartedUsers.get(userId);
if (uss == null) { // User is not started
// If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
@@ -1879,6 +1894,10 @@
// No matter what, the fact that we're requested to start the user (even if it is
// already running) puts it towards the end of the mUserLru list.
addUserToUserLru(userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
if (unlockListener != null) {
uss.mUnlockProgress.addListener(unlockListener);
@@ -1923,6 +1942,9 @@
// of mUserLru, so we need to ensure that the foreground user isn't displaced.
addUserToUserLru(mCurrentUserId);
}
+ if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
+ scheduleStopOfBackgroundUser(userId);
+ }
t.traceEnd();
// Make sure user is in the started state. If it is currently
@@ -2294,6 +2316,65 @@
}
}
+ /**
+ * Possibly schedules the user to be stopped at a future point. To be used to stop background
+ * users that haven't been actively used in a long time.
+ * This is only intended for full users that are currently in the background.
+ */
+ private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) {
+ if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ return;
+ }
+ final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs;
+ if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) {
+ // Feature is not enabled on this device.
+ return;
+ }
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ // Never stop system user
+ return;
+ }
+ if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) {
+ // MainUser is currently special for things like Docking, so we'll exempt it for now.
+ Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue "
+ + "of it being the main user", oldUserId);
+ return;
+ }
+ Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs);
+ final int delayUptimeMs = delayUptimeSecs * 1000;
+ final Object msgObj = oldUserId;
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj),
+ delayUptimeMs);
+ }
+
+ /**
+ * Possibly stops the given full user due to it having been in the background for a long time.
+ * There is no guarantee of stopping the user; it is done discretionarily.
+ *
+ * This should never be called for background visible users; devices that support this should
+ * not use {@link #scheduleStopOfBackgroundUser(int)}.
+ *
+ * @param userIdInteger a full user to be stopped if it is still in the background
+ */
+ @VisibleForTesting
+ void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
+ final int userId = userIdInteger;
+ Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);
+ synchronized (mLock) {
+ if (getCurrentOrTargetUserIdLU() == userId) {
+ return;
+ }
+ if (mPendingTargetUserIds.contains(userIdInteger)) {
+ // We'll soon want to switch to this user, so don't kill it now.
+ return;
+ }
+ Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
+ stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null);
+ }
+ }
+
private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -2428,6 +2509,7 @@
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ scheduleStopOfBackgroundUser(oldUserId);
t.traceEnd(); // end continueUserSwitch
}
@@ -3309,6 +3391,8 @@
pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch);
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
+ pw.println(" mBackgroundUserScheduledStopTimeSecs:"
+ + mBackgroundUserScheduledStopTimeSecs);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
pw.println(" mIsBroadcastSentForSystemUserStarted:"
@@ -3435,6 +3519,9 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1, msg.arg2);
break;
+ case SCHEDULED_STOP_BACKGROUND_USER_MSG:
+ processScheduledStopOfBackgroundUser((Integer) msg.obj);
+ break;
}
return false;
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index a17b3d5..ce41079 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -461,6 +461,9 @@
packageName, PACKAGE_MATCH_FLAGS, userId);
StorageStats stats = mStorageStatsManager.queryStatsForPackage(
info.storageUuid, packageName, new UserHandle(userId));
+ if (android.app.Flags.appRestrictionsApi()) {
+ noteHibernationChange(packageName, info.uid, true);
+ }
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
@@ -490,6 +493,11 @@
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ ApplicationInfo info = mIPackageManager.getApplicationInfo(
+ packageName, PACKAGE_MATCH_FLAGS, userId);
+ noteHibernationChange(packageName, info.uid, false);
+ }
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
@@ -555,6 +563,26 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */
+ private void noteHibernationChange(String packageName, int uid, boolean hibernated) {
+ try {
+ if (hibernated) {
+ // TODO: Switch to an ActivityManagerInternal API
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+ /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
+ } else {
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ false, ActivityManager.RESTRICTION_REASON_USAGE, null,
+ 0L);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't set restriction state change");
+ }
+ }
+
/**
* Initializes in-memory store of user-level hibernation states for the given user
*
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a1f80d0..b8bfeda 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4727,9 +4727,14 @@
}
if ((code == OP_CAMERA) && isAutomotive()) {
- if ((Flags.cameraPrivacyAllowlist())
- && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
- return true;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if ((Flags.cameraPrivacyAllowlist())
+ && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -5543,6 +5548,20 @@
}
return 0;
}
+ case "note": {
+ int res = shell.parseUserPackageOp(true, err);
+ if (res < 0) {
+ return res;
+ }
+ if (shell.packageName != null) {
+ shell.mInterface.noteOperation(shell.op, shell.packageUid,
+ shell.packageName, shell.attributionTag, true,
+ "appops note shell command", true);
+ } else {
+ return -1;
+ }
+ return 0;
+ }
case "start": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c11fbe1..add8491 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -176,6 +176,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.HwBinder;
import android.os.IBinder;
import android.os.Looper;
@@ -686,6 +687,9 @@
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ // Handler for broadcast receiver
+ // TODO(b/335513647) combine handlers
+ private final HandlerThread mBroadcastHandlerThread;
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
@@ -1121,6 +1125,9 @@
mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
+ mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
+ mBroadcastHandlerThread.start();
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1507,7 +1514,8 @@
intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null,
+ mBroadcastHandlerThread.getThreadHandler(),
Context.RECEIVER_EXPORTED);
SubscriptionManager subscriptionManager = mContext.getSystemService(
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 7a9491e..92fd9cb 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -9,8 +9,8 @@
}
flag {
- name: "de_hidl"
- namespace: "biometrics_framework"
- description: "feature flag for biometrics de-hidl"
- bug: "287332354"
-}
\ No newline at end of file
+ name: "use_vhal_for_testing"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
+ bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index beb3f2f..9c8d98d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -34,6 +34,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IVirtualHal;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
@@ -59,6 +60,7 @@
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -133,6 +135,8 @@
@Nullable private ISidefpsController mSidefpsController;
private final AuthSessionCoordinator mAuthSessionCoordinator;
@Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Nullable private IVirtualHal mVhal;
+ @Nullable private String mHalInstanceNameCurrent;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -293,10 +297,29 @@
@VisibleForTesting
synchronized IFingerprint getHalInstance() {
if (mTestHalEnabled) {
- // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
- // the test HAL for all sensors under that HAL. This can be updated in the future if
- // necessary.
- return new TestHal();
+ if (Flags.useVhalForTesting()) {
+ if (!mHalInstanceNameCurrent.contains("virtual")) {
+ Slog.i(getTag(), "Switching fingerprint hal from " + mHalInstanceName
+ + " to virtual hal");
+ mHalInstanceNameCurrent = "virtual";
+ mDaemon = null;
+ }
+ } else {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+ } else {
+ if (mHalInstanceNameCurrent == null) {
+ mHalInstanceNameCurrent = mHalInstanceName;
+ } else if (mHalInstanceNameCurrent.contains("virtual")
+ && mHalInstanceNameCurrent != mHalInstanceName) {
+ Slog.i(getTag(), "Switching fingerprint from virtual hal " + "to "
+ + mHalInstanceName);
+ mHalInstanceNameCurrent = mHalInstanceName;
+ mDaemon = null;
+ }
}
if (mDaemon != null) {
@@ -308,7 +331,7 @@
mDaemon = IFingerprint.Stub.asInterface(
Binder.allowBlocking(
ServiceManager.waitForDeclaredService(
- IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
+ IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent)));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -952,4 +975,26 @@
public void sendFingerprintReEnrollNotification() {
mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
}
+
+ /**
+ * Return virtual hal AIDL interface if it is used for testing
+ *
+ */
+ public IVirtualHal getVhal() throws RemoteException {
+ if (mVhal == null && useVhalForTesting()) {
+ mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
+ if (mVhal == null) {
+ Slog.e(getTag(), "Unable to get virtual hal interface");
+ }
+ }
+
+ return mVhal;
+ }
+
+ /**
+ * Return true if vhal_for_testing feature is enabled and test is active
+ */
+ public boolean useVhalForTesting() {
+ return (Flags.useVhalForTesting() && mTestHalEnabled);
+ }
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9f4b3d2..c393e92 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1651,7 +1651,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
+ case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index cfdb75f..896670e4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1406,42 +1406,47 @@
}
setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
- // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
- if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
- if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
- brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = clampScreenBrightness(brightnessState);
- // slowly adapt to auto-brightness
- // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
- slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
- && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
- brightnessAdjustmentFlags =
- mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+
+ if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+ mTempBrightnessEvent);
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ // slowly adapt to auto-brightness
+ // TODO(b/253226419): slowChange should be decided by
+ // strategy.updateBrightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
- setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- } else {
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
- // Restore the lower-priority brightness strategy
- brightnessState = displayBrightnessState.getBrightness();
}
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- } else {
- // Any non-auto-brightness values such as override or temporary should still be subject
- // to clamping so that they don't go beyond the current max as specified by Brightness
- // Range Controller.
+ }
+
+ if (!Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(brightnessState);
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
// If there's an offload session, we need to set the initial doze brightness before
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2583d73..808e296 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -289,6 +289,10 @@
@GuardedBy("ImfLock.class")
private int mCurrentUserId;
+ /** Holds all user related data */
+ @GuardedBy("ImfLock.class")
+ private UserDataRepository mUserDataRepository;
+
@MultiUserUnawareField
final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
@@ -1099,7 +1103,7 @@
imesToClearAdditionalSubtypes.add(imiId);
}
int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+ if (change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
if (isCurrentUser) {
setInputMethodEnabledLocked(imi.getId(), false);
@@ -1284,7 +1288,11 @@
public void onUserStarting(TargetUser user) {
// Called on ActivityManager thread.
SecureSettingsWrapper.onUserStarting(user.getUserIdentifier());
+ synchronized (ImfLock.class) {
+ mService.mUserDataRepository.getOrCreate(user.getUserIdentifier());
+ }
}
+
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1373,6 +1381,10 @@
AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+ mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal);
+ for (int id : mUserManagerInternal.getUserIds()) {
+ mUserDataRepository.getOrCreate(id);
+ }
final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
@@ -5856,6 +5868,7 @@
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
p.println(" " + c + ":");
p.println(" client=" + c.mClient);
+
p.println(" fallbackInputConnection="
+ c.mFallbackInputConnection);
p.println(" sessionRequested="
@@ -5864,6 +5877,9 @@
" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
p.println(" curSession=" + c.mCurSession);
+ p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+ p.println(" uid=" + c.mUid);
+ p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
p.println(" mCurrentUserId=" + mCurrentUserId);
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
new file mode 100644
index 0000000..7f00229
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.UserManagerInternal;
+
+import java.util.function.Consumer;
+
+final class UserDataRepository {
+
+ @GuardedBy("ImfLock.class")
+ private final SparseArray<UserData> mUserData = new SparseArray<>();
+
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ UserData getOrCreate(@UserIdInt int userId) {
+ UserData userData = mUserData.get(userId);
+ if (userData == null) {
+ userData = new UserData(userId);
+ mUserData.put(userId, userData);
+ }
+ return userData;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void forAllUserData(Consumer<UserData> consumer) {
+ for (int i = 0; i < mUserData.size(); i++) {
+ consumer.accept(mUserData.valueAt(i));
+ }
+ }
+
+ UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal) {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ mUserData.remove(userId);
+ }
+ });
+ }
+
+ @Override
+ public void onUserCreated(UserInfo user, Object unusedToken) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ getOrCreate(userId);
+ }
+ });
+ }
+ });
+ }
+
+ /** Placeholder for all IMMS user specific fields */
+ static final class UserData {
+ @UserIdInt
+ final int mUserId;
+
+ /**
+ * Intended to be instantiated only from this file.
+ */
+ private UserData(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 71a9f54..8002300 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.location.flags.Flags;
import android.net.ConnectivityManager;
@@ -32,12 +33,14 @@
import android.os.PowerManager;
import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.server.FgThread;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -200,7 +203,12 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
if (subManager != null) {
- subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ if (Flags.subscriptionsListenerThread()) {
+ subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
+ mOnSubscriptionsChangeListener);
+ } else {
+ subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ }
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -566,6 +574,10 @@
}
}
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.READ_PHONE_STATE
+ })
private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
mAGpsDataConnectionIpAddr = null;
mAGpsType = agpsType;
@@ -599,6 +611,19 @@
NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
+ if (com.android.internal.telephony.flags.Flags.satelliteInternet()) {
+ // Add transport type NetworkCapabilities.TRANSPORT_SATELLITE on satellite network.
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ if (telephonyManager != null) {
+ ServiceState state = telephonyManager.getServiceState();
+ if (state != null && state.isUsingNonTerrestrialNetwork()) {
+ networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+ }
+ }
+ }
+
// During an emergency call, and when we have cached the Active Sub Id, we set the
// Network Specifier so that the network request goes to the correct Sub Id
if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b48cad2..ebea05d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2514,7 +2514,8 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- mShowReviewPermissionsNotification);
+ mShowReviewPermissionsNotification,
+ Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
mSnoozeHelper = snoozeHelper;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 461bd9c..1f2ad07e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -93,6 +93,8 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -113,6 +115,8 @@
private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
@VisibleForTesting
static final int UNKNOWN_UID = UserHandle.USER_NULL;
+ // The amount of time pacakage preferences can exist without the app being installed.
+ private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis();
@VisibleForTesting
static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
@@ -149,6 +153,8 @@
private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
+ private static final String ATT_CREATION_TIME = "creation_time";
+
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -208,11 +214,13 @@
private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
private final boolean mShowReviewPermissionsNotification;
+ Clock mClock;
+
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- boolean showReviewPermissionsNotification) {
+ boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
mRankingHandler = rankingHandler;
@@ -225,7 +233,7 @@
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
-
+ mClock = clock;
XML_VERSION = 4;
updateBadgingEnabled();
@@ -309,7 +317,7 @@
parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
- bubblePref);
+ bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis()));
r.bubblePreference = bubblePref;
r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY);
r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY);
@@ -463,12 +471,12 @@
// TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE
return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
- DEFAULT_BUBBLE_PREFERENCE);
+ DEFAULT_BUBBLE_PREFERENCE, mClock.millis());
}
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
- boolean showBadge, int bubblePreference) {
+ boolean showBadge, int bubblePreference, long creationTime) {
final String key = packagePreferencesKey(pkg, uid);
PackagePreferences
r = (uid == UNKNOWN_UID)
@@ -483,6 +491,11 @@
r.visibility = visibility;
r.showBadge = showBadge;
r.bubblePreference = bubblePreference;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (r.uid == UNKNOWN_UID) {
+ r.creationTime = creationTime;
+ }
+ }
try {
createDefaultChannelIfNeededLocked(r);
@@ -496,6 +509,12 @@
mPackagePreferences.put(key, r);
}
}
+ if (r.uid == UNKNOWN_UID) {
+ if (Flags.persistIncompleteRestoreData()
+ && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) {
+ mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
+ }
+ }
return r;
}
@@ -590,70 +609,16 @@
if (forBackup && UserHandle.getUserId(r.uid) != userId) {
continue;
}
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, r.pkg);
- if (!notifPermissions.isEmpty()) {
- Pair<Integer, String> app = new Pair(r.uid, r.pkg);
- final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
- out.attributeInt(null, ATT_IMPORTANCE,
- permission != null && permission.first ? IMPORTANCE_DEFAULT
- : IMPORTANCE_NONE);
- notifPermissions.remove(app);
- } else {
- if (r.importance != DEFAULT_IMPORTANCE) {
- out.attributeInt(null, ATT_IMPORTANCE, r.importance);
- }
+ writePackageXml(r, out, notifPermissions, forBackup);
+ }
+ }
+ if (Flags.persistIncompleteRestoreData() && !forBackup) {
+ synchronized (mRestoredWithoutUids) {
+ final int N = mRestoredWithoutUids.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mRestoredWithoutUids.valueAt(i);
+ writePackageXml(r, out, notifPermissions, false);
}
- if (r.priority != DEFAULT_PRIORITY) {
- out.attributeInt(null, ATT_PRIORITY, r.priority);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attributeInt(null, ATT_VISIBILITY, r.visibility);
- }
- if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
- out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
- }
- out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
- out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
- r.lockedAppFields);
- out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
- r.hasSentInvalidMessage);
- out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
- r.hasSentValidMessage);
- out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
- r.userDemotedMsgApp);
- out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
-
- if (!forBackup) {
- out.attributeInt(null, ATT_UID, r.uid);
- }
-
- if (r.delegate != null) {
- out.startTag(null, TAG_DELEGATE);
-
- out.attribute(null, ATT_NAME, r.delegate.mPkg);
- out.attributeInt(null, ATT_UID, r.delegate.mUid);
- if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
- out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
- }
- out.endTag(null, TAG_DELEGATE);
- }
-
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeXml(out);
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (forBackup) {
- if (!channel.isDeleted()) {
- channel.writeXmlForBackup(out, mContext);
- }
- } else {
- channel.writeXml(out);
- }
- }
-
- out.endTag(null, TAG_PACKAGE);
}
}
// Some apps have permissions set but don't have expanded notification settings
@@ -669,6 +634,80 @@
out.endTag(null, TAG_RANKING);
}
+ public void writePackageXml(PackagePreferences r, TypedXmlSerializer out,
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions,
+ boolean forBackup) throws
+ IOException {
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (!notifPermissions.isEmpty()) {
+ Pair<Integer, String> app = new Pair(r.uid, r.pkg);
+ final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
+ out.attributeInt(null, ATT_IMPORTANCE,
+ permission != null && permission.first ? IMPORTANCE_DEFAULT
+ : IMPORTANCE_NONE);
+ notifPermissions.remove(app);
+ } else {
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ out.attributeInt(null, ATT_IMPORTANCE, r.importance);
+ }
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attributeInt(null, ATT_PRIORITY, r.priority);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attributeInt(null, ATT_VISIBILITY, r.visibility);
+ }
+ if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
+ out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
+ }
+ out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
+ out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
+ r.lockedAppFields);
+ out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
+ r.hasSentInvalidMessage);
+ out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
+ r.hasSentValidMessage);
+ out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+ r.userDemotedMsgApp);
+ out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+
+ if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
+ out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+ }
+
+ if (!forBackup) {
+ out.attributeInt(null, ATT_UID, r.uid);
+ }
+
+ if (r.delegate != null) {
+ out.startTag(null, TAG_DELEGATE);
+
+ out.attribute(null, ATT_NAME, r.delegate.mPkg);
+ out.attributeInt(null, ATT_UID, r.delegate.mUid);
+ if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+ out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
+ }
+ out.endTag(null, TAG_DELEGATE);
+ }
+
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
+ channel.writeXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_PACKAGE);
+ }
+
/**
* Sets whether bubbles are allowed.
*
@@ -2906,6 +2945,7 @@
boolean hasSentValidBubble = false;
boolean migrateToPm = false;
+ long creationTime;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 077ed5a..2859871 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -95,3 +95,9 @@
bug: "331967355"
}
+flag {
+ name: "persist_incomplete_restore_data"
+ namespace: "systemui"
+ description: "Stores restore data for not-yet-installed pkgs for 48 hours"
+ bug: "334999659"
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2a3b939..d41727f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -48,6 +48,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.PackageManagerException.INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
@@ -1050,6 +1051,20 @@
request.setError("Scanning Failed.", e);
return;
}
+ if (request.isArchived()) {
+ final SparseArray<String> responsibleInstallerTitles =
+ PackageArchiver.getResponsibleInstallerTitles(mContext,
+ mPm.snapshotComputer(), request.getInstallSource(),
+ request.getUserId(), mPm.mUserManager.getUserIds());
+ if (responsibleInstallerTitles == null
+ || responsibleInstallerTitles.size() == 0) {
+ request.setError(PackageManagerException.ofInternalError(
+ "Failed to obtain the responsible installer info",
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
+ return;
+ }
+ request.setResponsibleInstallerTitles(responsibleInstallerTitles);
+ }
}
List<ReconciledPackage> reconciledPackages;
@@ -2226,6 +2241,7 @@
// to figure out which users were changed.
mPm.markPackageAsArchivedIfNeeded(ps,
installRequest.getArchivedPackage(),
+ installRequest.getResponsibleInstallerTitles(),
installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4dcee04..6d38517 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -49,6 +49,7 @@
import android.util.ArrayMap;
import android.util.ExceptionUtils;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
@@ -130,6 +131,12 @@
@Nullable
private String mApexModuleName;
+ /**
+ * The title of the responsible installer for the archive behavior used
+ */
+ @Nullable
+ private SparseArray<String> mResponsibleInstallerTitles;
+
@Nullable
private ScanResult mScanResult;
@@ -418,6 +425,12 @@
public String getApexModuleName() {
return mApexModuleName;
}
+
+ @Nullable
+ public SparseArray<String> getResponsibleInstallerTitles() {
+ return mResponsibleInstallerTitles;
+ }
+
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
@@ -756,6 +769,11 @@
mApexModuleName = apexModuleName;
}
+ public void setResponsibleInstallerTitles(
+ @NonNull SparseArray<String> responsibleInstallerTitles) {
+ mResponsibleInstallerTitles = responsibleInstallerTitles;
+ }
+
public void setPkg(AndroidPackage pkg) {
mPkg = pkg;
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index fda4dc6..9bdf613 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -85,13 +85,13 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ExceptionUtils;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -463,8 +463,10 @@
final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>();
mPm.mHandler.post(() -> {
try {
+ final String installerTitle = getResponsibleInstallerTitle(
+ mContext, installerInfo, responsibleInstallerPackage, userId);
var archiveState = createArchiveStateInternal(packageName, userId, mainActivities,
- installerInfo.loadLabel(mContext.getPackageManager()).toString());
+ installerTitle);
storeArchiveState(packageName, archiveState, userId);
archiveStateStored.complete(null);
} catch (IOException | PackageManager.NameNotFoundException e) {
@@ -476,7 +478,7 @@
@Nullable
ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
- int userId, String installerPackage) {
+ int userId, String installerPackage, String responsibleInstallerTitle) {
ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo(
installerPackage, /* flags= */ 0, userId);
if (installerInfo == null) {
@@ -484,6 +486,11 @@
Slog.e(TAG, "Couldn't find installer " + installerPackage);
return null;
}
+ if (responsibleInstallerTitle == null) {
+ Slog.e(TAG, "Couldn't get the title of the installer");
+ return null;
+ }
+
final int iconSize = mContext.getSystemService(
ActivityManager.class).getLauncherLargeIconSize();
@@ -508,8 +515,7 @@
archiveActivityInfos.add(activityInfo);
}
- return new ArchiveState(archiveActivityInfos,
- installerInfo.loadLabel(mContext.getPackageManager()).toString());
+ return new ArchiveState(archiveActivityInfos, responsibleInstallerTitle);
} catch (IOException e) {
Slog.e(TAG, "Failed to create archive state", e);
return null;
@@ -1106,10 +1112,61 @@
return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
}
+ private static String getResponsibleInstallerPackage(InstallSource installSource) {
+ return TextUtils.isEmpty(installSource.mUpdateOwnerPackageName)
+ ? installSource.mInstallerPackageName
+ : installSource.mUpdateOwnerPackageName;
+ }
+
+ private static String getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo,
+ String responsibleInstallerPackage, int userId)
+ throws PackageManager.NameNotFoundException {
+ final Context userContext = context.createPackageContextAsUser(
+ responsibleInstallerPackage, /* flags= */ 0, new UserHandle(userId));
+ return appInfo.loadLabel(userContext.getPackageManager()).toString();
+ }
+
static String getResponsibleInstallerPackage(PackageStateInternal ps) {
- return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName)
- ? ps.getInstallSource().mInstallerPackageName
- : ps.getInstallSource().mUpdateOwnerPackageName;
+ return getResponsibleInstallerPackage(ps.getInstallSource());
+ }
+
+ @Nullable
+ static SparseArray<String> getResponsibleInstallerTitles(Context context, Computer snapshot,
+ InstallSource installSource, int requestUserId, int[] allUserIds) {
+ final String responsibleInstallerPackage = getResponsibleInstallerPackage(installSource);
+ final SparseArray<String> responsibleInstallerTitles = new SparseArray<>();
+ try {
+ if (requestUserId != UserHandle.USER_ALL) {
+ final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
+ responsibleInstallerPackage, /* flags= */ 0, requestUserId);
+ if (responsibleInstallerInfo == null) {
+ return null;
+ }
+
+ final String title = getResponsibleInstallerTitle(context,
+ responsibleInstallerInfo, responsibleInstallerPackage, requestUserId);
+ responsibleInstallerTitles.put(requestUserId, title);
+ } else {
+ // Go through all userIds.
+ for (int i = 0; i < allUserIds.length; i++) {
+ final int userId = allUserIds[i];
+ final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
+ responsibleInstallerPackage, /* flags= */ 0, userId);
+ // Can't get the applicationInfo on the user.
+ // Maybe the installer isn't installed on the user.
+ if (responsibleInstallerInfo == null) {
+ continue;
+ }
+
+ final String title = getResponsibleInstallerTitle(context,
+ responsibleInstallerInfo, responsibleInstallerPackage, userId);
+ responsibleInstallerTitles.put(userId, title);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ return null;
+ }
+ return responsibleInstallerTitles;
}
void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index d69737a..9206759 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -64,6 +64,7 @@
public static final int INTERNAL_ERROR_APEX_NOT_DIRECTORY = -36;
public static final int INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE = -37;
public static final int INTERNAL_ERROR_MISSING_USER = -38;
+ public static final int INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE = -39;
@IntDef(prefix = { "INTERNAL_ERROR_" }, value = {
INTERNAL_ERROR_NATIVE_LIBRARY_COPY,
@@ -103,7 +104,8 @@
INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS,
INTERNAL_ERROR_APEX_NOT_DIRECTORY,
INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE,
- INTERNAL_ERROR_MISSING_USER
+ INTERNAL_ERROR_MISSING_USER,
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalErrorCode {}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 614828a..0f4e482 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1523,10 +1523,12 @@
}
void markPackageAsArchivedIfNeeded(PackageSetting pkgSetting,
- ArchivedPackageParcel archivePackage, int[] userIds) {
+ ArchivedPackageParcel archivePackage, SparseArray<String> responsibleInstallerTitles,
+ int[] userIds) {
if (pkgSetting == null || archivePackage == null
- || archivePackage.archivedActivities == null || userIds == null
- || userIds.length == 0) {
+ || archivePackage.archivedActivities == null
+ || responsibleInstallerTitles == null
+ || userIds == null || userIds.length == 0) {
return;
}
@@ -1552,7 +1554,8 @@
}
for (int userId : userIds) {
var archiveState = mInstallerService.mPackageArchiver.createArchiveState(
- archivePackage, userId, responsibleInstallerPackage);
+ archivePackage, userId, responsibleInstallerPackage,
+ responsibleInstallerTitles.get(userId));
if (archiveState != null) {
pkgSetting
.modifyUserState(userId)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index bd0501d..20c5b5f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -462,6 +462,11 @@
}
@Override
+ public int getNumRegisteredAttributionSources(int uid) {
+ return mAttributionSourceRegistry.getNumRegisteredAttributionSources(uid);
+ }
+
+ @Override
public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
}
@@ -938,6 +943,26 @@
}
}
+ public int getNumRegisteredAttributionSources(int uid) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_APP_OPS_STATS,
+ "getting the number of registered AttributionSources requires "
+ + "UPDATE_APP_OPS_STATS");
+ // Influence the system to perform a garbage collection, so the provided number is as
+ // accurate as possible
+ System.gc();
+ System.gc();
+ synchronized (mLock) {
+ int[] numForUid = { 0 };
+ mAttributions.forEach((key, value) -> {
+ if (value.getUid() == uid) {
+ numForUid[0]++;
+ }
+
+ });
+ return numForUid[0];
+ }
+ }
+
private int resolveUid(int uid) {
final VoiceInteractionManagerInternal vimi = LocalServices
.getService(VoiceInteractionManagerInternal.class);
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index a790950..af1ad13 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -34,7 +34,9 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
+import android.view.Display;
import android.view.KeyEvent;
+import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.Clock;
@@ -48,6 +50,7 @@
private final Context mContext;
private final PowerManager mPowerManager;
+ private final WindowManager mWindowManager;
private final Clock mClock;
private final boolean mAllowTheaterModeWakeFromKey;
@@ -68,6 +71,7 @@
WindowWakeUpPolicy(Context context, Clock clock) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mWindowManager = context.getSystemService(WindowManager.class);
mClock = clock;
final Resources res = context.getResources();
@@ -212,12 +216,23 @@
}
private boolean canWakeUp(boolean wakeInTheaterMode) {
+ if (supportInputWakeupDelegate() && isDefaultDisplayOn()) {
+ // If the default display is on, theater mode should not influence whether or not
+ // waking up is allowed. This is because the theater mode checks are there to block
+ // the display from being on in situations where the user may not want it to be
+ // on (so if the display is already on, no need to check for theater mode at all).
+ return true;
+ }
final boolean isTheaterModeEnabled =
Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
return wakeInTheaterMode || !isTheaterModeEnabled;
}
+ private boolean isDefaultDisplayOn() {
+ return Display.isOnState(mWindowManager.getDefaultDisplay().getState());
+ }
+
/** Wakes up {@link PowerManager}. */
private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
deleted file mode 100644
index 5d4065d..0000000
--- a/services/core/java/com/android/server/power/Android.bp
+++ /dev/null
@@ -1,14 +0,0 @@
-aconfig_declarations {
- name: "backstage_power_flags",
- package: "com.android.server.power.optimization",
- container: "system",
- srcs: [
- "stats/*.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "backstage_power_flags_lib",
- aconfig_declarations: "backstage_power_flags",
- sdk_version: "system_current",
-}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
rename to services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
rename to services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5b501e1..587be07 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -16,6 +16,7 @@
package com.android.server.security;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -27,6 +28,7 @@
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -79,7 +81,11 @@
return LocalServices.getService(FileIntegrityService.class);
}
- private final IBinder mService = new IFileIntegrityService.Stub() {
+ private final class BinderService extends IFileIntegrityService.Stub {
+ BinderService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ }
+
@Override
public boolean isApkVeritySupported() {
return VerityUtils.isFsVeritySupported();
@@ -168,12 +174,10 @@
}
@Override
+ @EnforcePermission(android.Manifest.permission.SETUP_FSVERITY)
public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
String filePath, String packageName) throws RemoteException {
- getContext().enforceCallingPermission(android.Manifest.permission.SETUP_FSVERITY,
- "Permission android.permission.SETUP_FSVERITY not grantted to access "
- + "FileIntegrityManager#setupFsverity");
-
+ setupFsverity_enforcePermission();
Objects.requireNonNull(authToken);
Objects.requireNonNull(filePath);
Objects.requireNonNull(packageName);
@@ -185,10 +189,12 @@
throw new RemoteException(e);
}
}
- };
+ }
+ private final IBinder mService;
public FileIntegrityService(final Context context) {
super(context);
+ mService = new BinderService(context);
try {
sCertFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
index e89ddfd..34d18ce 100644
--- a/services/core/java/com/android/server/selinux/QuotaLimiter.java
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -34,10 +34,10 @@
private final Clock mClock;
private final Duration mWindowSize;
- private final int mMaxPermits;
- private long mCurrentWindow = 0;
- private int mPermitsGranted = 0;
+ private int mMaxPermits;
+ private long mCurrentWindow;
+ private int mPermitsGranted;
@VisibleForTesting
QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
@@ -75,4 +75,8 @@
return false;
}
+
+ public void setMaxPermits(int maxPermits) {
+ this.mMaxPermits = maxPermits;
+ }
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
index 8d8d596..d69150d 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -15,35 +15,66 @@
*/
package com.android.server.selinux;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
/** Builder for SelinuxAuditLogs. */
class SelinuxAuditLogBuilder {
- // Currently logs collection is hardcoded for the sdk_sandbox_audit.
- private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
- static final Matcher SCONTEXT_MATCHER =
- Pattern.compile(
- "u:r:(?<stype>"
- + SDK_SANDBOX_AUDIT
- + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
- .matcher("");
+ private static final String TAG = "SelinuxAuditLogs";
- static final Matcher TCONTEXT_MATCHER =
- Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
- .matcher("");
+ // This config indicates which Selinux logs for source domains to collect. The string will be
+ // inserted into a regex, so it must follow the regex syntax. For example, a valid value would
+ // be "system_server|untrusted_app".
+ @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain";
+ private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher("");
+ private static final String TCONTEXT_PATTERN =
+ "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*";
+ private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"";
- static final Matcher PATH_MATCHER =
- Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+ @VisibleForTesting final Matcher mScontextMatcher;
+ @VisibleForTesting final Matcher mTcontextMatcher;
+ @VisibleForTesting final Matcher mPathMatcher;
private Iterator<String> mTokens;
private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+ SelinuxAuditLogBuilder() {
+ Matcher scontextMatcher = NO_OP_MATCHER;
+ Matcher tcontextMatcher = NO_OP_MATCHER;
+ Matcher pathMatcher = NO_OP_MATCHER;
+ try {
+ scontextMatcher =
+ Pattern.compile(
+ TextUtils.formatSimple(
+ "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*",
+ DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_DOMAIN,
+ "no_match^")))
+ .matcher("");
+ tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher("");
+ pathMatcher = Pattern.compile(PATH_PATTERN).matcher("");
+ } catch (PatternSyntaxException e) {
+ Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e);
+ }
+
+ mScontextMatcher = scontextMatcher;
+ mTcontextMatcher = tcontextMatcher;
+ mPathMatcher = pathMatcher;
+ }
+
void reset(String denialString) {
mTokens =
Arrays.asList(
@@ -82,18 +113,18 @@
mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
break;
case "scontext":
- if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mScontextMatcher)) {
return null;
}
- mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
- mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+ mAuditLog.mSType = mScontextMatcher.group("stype");
+ mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories"));
break;
case "tcontext":
- if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mTcontextMatcher)) {
return null;
}
- mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
- mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+ mAuditLog.mTType = mTcontextMatcher.group("ttype");
+ mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories"));
break;
case "tclass":
if (!mTokens.hasNext()) {
@@ -102,8 +133,8 @@
mAuditLog.mTClass = mTokens.next();
break;
case "path":
- if (nextTokenMatches(PATH_MATCHER)) {
- mAuditLog.mPath = PATH_MATCHER.group("path");
+ if (nextTokenMatches(mPathMatcher)) {
+ mAuditLog.mPath = mPathMatcher.group("path");
}
break;
case "permissive":
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 03822aa..c655d46 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -18,10 +18,12 @@
import android.util.EventLog;
import android.util.EventLog.Event;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import com.android.server.utils.Slogf;
import java.io.IOException;
import java.time.Instant;
@@ -37,6 +39,7 @@
class SelinuxAuditLogsCollector {
private static final String TAG = "SelinuxAuditLogs";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
@@ -48,13 +51,17 @@
@VisibleForTesting Instant mLastWrite = Instant.MIN;
- final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+ AtomicBoolean mStopRequested = new AtomicBoolean(false);
SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
mRateLimiter = rateLimiter;
mQuotaLimiter = quotaLimiter;
}
+ public void setStopRequested(boolean stopRequested) {
+ mStopRequested.set(stopRequested);
+ }
+
/**
* Collect and push SELinux audit logs for the provided {@code tagCode}.
*
@@ -66,7 +73,7 @@
boolean quotaExceeded = writeAuditLogs(logLines);
if (quotaExceeded) {
- Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+ Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
mLastWrite = latestTimestamp; // next run we will ignore all these logs.
logLines.clear();
}
@@ -79,7 +86,7 @@
try {
EventLog.readEvents(new int[] {tagCode}, events);
} catch (IOException e) {
- Log.e(TAG, "Error reading event logs", e);
+ Slog.e(TAG, "Error reading event logs", e);
}
Instant latestTimestamp = mLastWrite;
@@ -102,6 +109,7 @@
private boolean writeAuditLogs(Queue<Event> logLines) {
final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+ int auditsWritten = 0;
while (!mStopRequested.get() && !logLines.isEmpty()) {
Event event = logLines.poll();
@@ -118,6 +126,9 @@
}
if (!mQuotaLimiter.acquire()) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
+ }
return true;
}
mRateLimiter.acquire();
@@ -133,12 +144,16 @@
auditLog.mTClass,
auditLog.mPath,
auditLog.mPermissive);
+ auditsWritten++;
if (logTime.isAfter(mLastWrite)) {
mLastWrite = logTime;
}
}
+ if (DEBUG) {
+ Slogf.d(TAG, "Written %d logs", auditsWritten);
+ }
return false;
}
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
new file mode 100644
index 0000000..0092c37
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.selinux;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class handles the start and stop requests for the logs collector job, in particular making
+ * sure that at most one job is running at any given moment.
+ */
+final class SelinuxAuditLogsJob {
+
+ private static final String TAG = "SelinuxAuditLogs";
+
+ private final AtomicBoolean mIsRunning = new AtomicBoolean(false);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector;
+
+ SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) {
+ mAuditLogsCollector = auditLogsCollector;
+ }
+
+ void requestStop() {
+ mAuditLogsCollector.mStopRequested.set(true);
+ }
+
+ boolean isRunning() {
+ return mIsRunning.get();
+ }
+
+ public void start(JobService jobService, JobParameters params) {
+ mAuditLogsCollector.mStopRequested.set(false);
+ if (mIsRunning.get()) {
+ Slog.i(TAG, "Selinux audit job is already running, ignore start request.");
+ return;
+ }
+ mIsRunning.set(true);
+ boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
+ if (done) {
+ jobService.jobFinished(params, /* wantsReschedule= */ false);
+ }
+ mIsRunning.set(false);
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
index 8a661bc..d46e891 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -23,14 +23,16 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
import java.time.Duration;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
@@ -43,58 +45,68 @@
static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+ private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS =
+ "selinux_audit_job_frequency_hours";
+ private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
+ private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
+ private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
+
private static final int SELINUX_AUDIT_JOB_ID = 25327386;
- private static final JobInfo SELINUX_AUDIT_JOB =
- new JobInfo.Builder(
- SELINUX_AUDIT_JOB_ID,
- new ComponentName("android", SelinuxAuditLogsService.class.getName()))
- .setPeriodic(TimeUnit.DAYS.toMillis(1))
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setRequiresBatteryNotLow(true)
- .build();
+ private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT =
+ new ComponentName("android", SelinuxAuditLogsService.class.getName());
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
- private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
- // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
- // milliseconds, and no more than 50K atoms can be pushed each day.
- private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
- new SelinuxAuditLogsCollector(
- new RateLimiter(/* window= */ Duration.ofMillis(10)),
- new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+ // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure
+ // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the
+ // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static
+ // because new job executions happen in a new instance of this class. Making the quota limiter
+ // an instance reference would reset the quota limitations between jobs executions.
+ private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+ private static final QuotaLimiter QUOTA_LIMITER =
+ new QuotaLimiter(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_CAP,
+ MAX_PERMITS_CAP_DEFAULT));
+ private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB =
+ new SelinuxAuditLogsJob(
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER));
/** Schedule jobs with the {@link JobScheduler}. */
public static void schedule(Context context) {
if (!selinuxSdkSandboxAudit()) {
- Log.d(TAG, "SelinuxAuditLogsService not enabled");
+ Slog.d(TAG, "SelinuxAuditLogsService not enabled");
return;
}
if (AUDITD_TAG_CODE == -1) {
- Log.e(TAG, "auditd is not a registered tag on this system");
+ Slog.e(TAG, "auditd is not a registered tag on this system");
return;
}
- if (context.getSystemService(JobScheduler.class)
- .forNamespace(SELINUX_AUDIT_NAMESPACE)
- .schedule(SELINUX_AUDIT_JOB)
- == JobScheduler.RESULT_FAILURE) {
- Log.e(TAG, "SelinuxAuditLogsService could not be started.");
- }
+ LogsCollectorJobScheduler propertiesListener =
+ new LogsCollectorJobScheduler(
+ context.getSystemService(JobScheduler.class)
+ .forNamespace(SELINUX_AUDIT_NAMESPACE));
+ propertiesListener.schedule();
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener);
}
@Override
public boolean onStartJob(JobParameters params) {
if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
- Log.e(TAG, "The job id does not match the expected selinux job id.");
+ Slog.e(TAG, "The job id does not match the expected selinux job id.");
+ return false;
+ }
+ if (!selinuxSdkSandboxAudit()) {
+ Slog.i(TAG, "Selinux audit job disabled.");
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
- IS_RUNNING.set(true);
- EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
-
+ EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params));
return true; // the job is running
}
@@ -104,29 +116,69 @@
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
- return IS_RUNNING.get();
+ if (LOGS_COLLECTOR_JOB.isRunning()) {
+ LOGS_COLLECTOR_JOB.requestStop();
+ return true;
+ }
+ return false;
}
- private static class LogsCollectorJob implements Runnable {
- private final JobService mAuditLogService;
- private final JobParameters mParams;
+ /**
+ * This class is in charge of scheduling the job service, and keeping the scheduling up to date
+ * when the parameters change.
+ */
+ private static final class LogsCollectorJobScheduler
+ implements DeviceConfig.OnPropertiesChangedListener {
- LogsCollectorJob(JobService auditLogService, JobParameters params) {
- mAuditLogService = auditLogService;
- mParams = params;
+ private final JobScheduler mJobScheduler;
+
+ private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
+ mJobScheduler = jobScheduler;
}
@Override
- public void run() {
- IS_RUNNING.updateAndGet(
- isRunning -> {
- boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
- if (done) {
- mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
- }
- return !done;
- });
+ public void onPropertiesChanged(Properties changedProperties) {
+ Set<String> keyset = changedProperties.getKeyset();
+
+ if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) {
+ QUOTA_LIMITER.setMaxPermits(
+ changedProperties.getInt(
+ CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT));
+ }
+
+ if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
+ boolean enabled =
+ changedProperties.getBoolean(
+ CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false);
+ if (enabled) {
+ schedule();
+ } else {
+ mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+ }
+ } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
+ // The job frequency changed, reschedule.
+ schedule();
+ }
+ }
+
+ private void schedule() {
+ long frequencyMillis =
+ TimeUnit.HOURS.toMillis(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS,
+ 24));
+ if (mJobScheduler.schedule(
+ new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT)
+ .setPeriodic(frequencyMillis)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .build())
+ == JobScheduler.RESULT_FAILURE) {
+ Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled.");
+ } else {
+ Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully.");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b5df30f..e3e478d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -61,6 +61,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -355,7 +356,17 @@
private TelephonyManager mTelephony;
private UwbManager mUwbManager;
private SubscriptionManager mSubscriptionManager;
- private NetworkStatsManager mNetworkStatsManager;
+
+ /**
+ * NetworkStatsManager initialization happens from one thread before any worker thread
+ * is going to access the networkStatsManager instance:
+ * - @initNetworkStatsManager() - initialization happens no worker thread to access are
+ * active yet
+ * - @initAndRegisterNetworkStatsPullers Network stats dependant pullers can only be
+ * initialized after service is ready. Worker thread is spawn here only after the
+ * initialization is completed in a thread safe way (no async access expected)
+ */
+ private NetworkStatsManager mNetworkStatsManager = null;
@GuardedBy("mKernelWakelockLock")
private KernelWakelockReader mKernelWakelockReader;
@@ -420,6 +431,12 @@
public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
addMobileBytesTransferByProcStatePuller();
+ /**
+ * Whether or not to enable the mNetworkStatsManager initialization order fix
+ */
+ private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
+ statsPullNetworkStatsManagerInitOrderFix();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -823,6 +840,9 @@
registerEventListeners();
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
BackgroundThread.getHandler().post(() -> {
// Network stats related pullers can only be initialized after service is ready.
initAndRegisterNetworkStatsPullers();
@@ -843,7 +863,9 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
- mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1019,6 +1041,24 @@
}
}
+ /**
+ * Calling getNetworkStatsManager() before PHASE_THIRD_PARTY_APPS_CAN_START is unexpected
+ * Callers use before PHASE_THIRD_PARTY_APPS_CAN_START stage is not legit
+ */
+ @NonNull
+ private NetworkStatsManager getNetworkStatsManager() {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ if (mNetworkStatsManager == null) {
+ throw new IllegalStateException("NetworkStatsManager is not ready");
+ }
+ }
+ return mNetworkStatsManager;
+ }
+
+ private void initNetworkStatsManager() {
+ mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1514,11 +1554,11 @@
// I/O and also block main thread when polling.
// Consider making perfd queries NetworkStatsService directly.
if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
- mNetworkStatsManager.forceUpdate();
+ getNetworkStatsManager().forceUpdate();
}
final android.app.usage.NetworkStats queryNonTaggedStats =
- mNetworkStatsManager.querySummary(
+ getNetworkStatsManager().querySummary(
template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
@@ -1528,7 +1568,7 @@
if (!includeTags) return nonTaggedStats;
final android.app.usage.NetworkStats queryTaggedStats =
- mNetworkStatsManager.queryTaggedSummary(template,
+ getNetworkStatsManager().queryTaggedSummary(template,
currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
final NetworkStats taggedStats =
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 101b98e..c479c6d 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -7,4 +7,12 @@
description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
bug: "309512867"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "stats_pull_network_stats_manager_init_order_fix"
+ namespace: "statsd"
+ description: "Fix the mNetworkStatsManager initialization order"
+ bug: "331989853"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 4a81c95..440d251 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -89,8 +89,34 @@
* @param ownerId the removing client id of the owner.
*/
public void removeOwner(int ownerId) {
- mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
- mOwnerClientIdsToSessionNum.remove(ownerId);
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+ mOwnerClientIdsToSessionNum.remove(ownerId);
+ }
+ }
+
+ /**
+ * Remove a single session from resource
+ *
+ * @param ownerId the client Id of the owner of the session
+ */
+ public void removeSession(int ownerId) {
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId);
+ if (sessionNum > 0) {
+ mOwnerClientIdsToSessionNum.put(ownerId, --sessionNum);
+ mAvailableSessionNum++;
+ }
+ }
+ }
+
+ /**
+ * Check if there are any open sessions owned by a client
+ *
+ * @param ownerId the client Id of the owner of the sessions
+ */
+ public boolean hasOpenSessions(int ownerId) {
+ return mOwnerClientIdsToSessionNum.get(ownerId) > 0;
}
public Set<Integer> getOwnerClientIds() {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index cddc79d..0afb049 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1924,11 +1924,13 @@
ownerProfile.useCiCam(grantingId);
}
- private void updateCasClientMappingOnRelease(
- @NonNull CasResource releasingCas, int ownerClientId) {
- ClientProfile ownerProfile = getClientProfile(ownerClientId);
- releasingCas.removeOwner(ownerClientId);
- ownerProfile.releaseCas();
+ private void updateCasClientMappingOnRelease(@NonNull CasResource cas, int ownerClientId) {
+ cas.removeSession(ownerClientId);
+ if (!cas.hasOpenSessions(ownerClientId)) {
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ cas.removeOwner(ownerClientId);
+ ownerProfile.releaseCas();
+ }
}
private void updateCiCamClientMappingOnRelease(
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index b7d8cfc..e944eca 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -193,6 +193,10 @@
@GuardedBy("mLock")
private int mTotalStarted = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -434,10 +438,10 @@
@Override
void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
synchronized (mLock) {
- if (mTimerIdMap.containsKey(arg)) {
- // There is an existing timer. Cancel it.
- cancel(arg);
- }
+ // If there is an existing timer, cancel it. This is a nop if the timer does not
+ // exist.
+ if (cancel(arg)) mTotalRestarted++;
+
int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
if (timerId > 0) {
mTimerIdMap.put(arg, timerId);
@@ -546,9 +550,7 @@
private Integer removeLocked(V arg) {
Integer r = mTimerIdMap.remove(arg);
if (r != null) {
- synchronized (mTimerArgMap) {
- mTimerArgMap.remove(r);
- }
+ mTimerArgMap.remove(r);
}
return r;
}
@@ -672,8 +674,8 @@
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
mTotalExpired, mTotalErrors);
pw.decreaseIndent();
mFeature.dump(pw, false);
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 1383708..6a4c9c2 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.IpSecTransformState;
import android.net.vcn.FeatureFlags;
import android.net.vcn.FeatureFlagsImpl;
import android.os.Looper;
@@ -34,7 +35,6 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,6 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
@@ -76,7 +75,16 @@
}
public boolean isFlagIpSecTransformStateEnabled() {
- return mCoreNetFeatureFlags.ipsecTransformState();
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ try {
+ new IpSecTransformState.Builder();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index b33fa6f..f82ff67 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -101,7 +101,9 @@
}
@Override
- public void registerVibratorController(IVibratorController controller) {
+ public void registerVibratorController(@NonNull IVibratorController controller) {
+ Objects.requireNonNull(controller);
+
synchronized (mLock) {
mVibratorControllerHolder.setVibratorController(controller);
}
@@ -134,6 +136,7 @@
public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
@NonNull IVibratorController token) {
Objects.requireNonNull(token);
+ requireContainsNoNullElement(params);
synchronized (mLock) {
if (mVibratorControllerHolder.getVibratorController() == null) {
@@ -148,6 +151,13 @@
+ "controller doesn't match the registered one. " + this);
return;
}
+ if (params == null) {
+ // Adaptive haptics scales cannot be set to null. Ignoring request.
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
updateAdaptiveHapticsScales(params);
recordUpdateVibrationParams(params, /* fromRequest= */ false);
@@ -181,6 +191,7 @@
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
Objects.requireNonNull(requestToken);
+ requireContainsNoNullElement(result);
synchronized (mLock) {
if (mVibrationParamRequest == null) {
@@ -202,6 +213,13 @@
long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+ if (result == null) {
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
+
updateAdaptiveHapticsScales(result);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -401,10 +419,9 @@
*
* @param params the new vibration params.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null) {
- return;
- }
+ private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) {
+ Objects.requireNonNull(params);
+
for (VibrationParam param : params) {
if (param.getTag() != VibrationParam.scale) {
Slog.e(TAG, "Unsupported vibration param: " + param);
@@ -448,11 +465,10 @@
mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
}
- private void recordUpdateVibrationParams(@Nullable VibrationParam[] params,
+ private void recordUpdateVibrationParams(@NonNull VibrationParam[] params,
boolean fromRequest) {
- if (params == null) {
- return;
- }
+ Objects.requireNonNull(params);
+
VibrationParamsRecords.Operation operation =
fromRequest ? VibrationParamsRecords.Operation.PULL
: VibrationParamsRecords.Operation.PUSH;
@@ -474,6 +490,13 @@
VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
}
+ private void requireContainsNoNullElement(VibrationParam[] params) {
+ if (ArrayUtils.contains(params, null)) {
+ throw new IllegalArgumentException(
+ "Invalid vibration params received: null values are not permitted.");
+ }
+ }
+
/**
* Keep records of {@link VibrationParam} values received by this service from a registered
* {@link VibratorController} and provide debug information for this service.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bf094ed..54e932a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,6 +658,8 @@
*/
private CompatDisplayInsets mCompatDisplayInsets;
+ private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
@@ -819,6 +821,12 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+ // aspect ratio. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+ @Nullable
+ private Rect mLetterboxBoundsForAspectRatio;
+
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -2110,6 +2118,10 @@
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+ mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds =
+ mWmService.mFlags.mInsetsDecoupledConfiguration
+ && !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
mTargetSdk = info.applicationInfo.targetSdkVersion;
@@ -8436,10 +8448,15 @@
fullConfig.windowConfiguration.getRotation());
}
+ final Rect letterboxedContainerBounds =
+ mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+ : mLetterboxBoundsForAspectRatio;
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
new CompatDisplayInsets(
- mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
+ mDisplayContent, this, letterboxedContainerBounds,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
}
private void clearSizeCompatModeAttributes() {
@@ -8511,6 +8528,7 @@
mIsAspectRatioApplied = false;
mIsEligibleForFixedOrientationLetterbox = false;
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mLetterboxBoundsForAspectRatio = null;
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8546,13 +8564,14 @@
// If the activity has requested override bounds, the configuration needs to be
// computed accordingly.
if (!matchParentBounds()) {
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
- newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
- // are already calculated in resolveFixedOrientationConfiguration.
+ // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+ // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ }
+ if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
&& !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8636,16 +8655,15 @@
if (mDisplayContent == null) {
return;
}
- final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mWmService.mFlags.mInsetsDecoupledConfiguration
- || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds
|| getCompatDisplayInsets() != null
- || isFloating(parentWindowingMode) || fullBounds == null
- || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ || isFloating(parentWindowingMode) || parentAppBounds == null
+ || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8653,15 +8671,20 @@
}
// Override starts here.
- final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
- rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+ inOutConfig.windowConfiguration.setAppBounds(parentAppBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(stableInsets);
+ outAppBounds.inset(nonDecorInsets);
}
float density = inOutConfig.densityDpi;
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
@@ -8685,11 +8708,10 @@
// For the case of PIP transition and multi-window environment, the
// smallestScreenWidthDp is handled already. Override only if the app is in
// fullscreen.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
DisplayInfo info = new DisplayInfo();
mDisplayContent.getDisplay().getDisplayInfo(info);
- mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
- info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+ mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+ mDisplayContent.getDisplayMetrics().density,
inOutConfig, true /* overrideConfig */);
}
@@ -8702,14 +8724,12 @@
}
}
- /**
- * @return The orientation to use to understand if reachability is enabled.
- */
- @Configuration.Orientation
- int getOrientationForReachability() {
- return mLetterboxUiController.hasInheritedLetterboxBehavior()
- ? mLetterboxUiController.getInheritedOrientation()
- : getRequestedConfigurationOrientation();
+ private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+ @NonNull Configuration parentConfig) {
+ task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
+ // Reset the temp info which should only take effect for the specified computation.
+ mResolveConfigHint.mTmpCompatInsets = null;
+ mResolveConfigHint.mTmpOverrideDisplayInfo = null;
}
/**
@@ -8852,7 +8872,7 @@
}
// Since bounds has changed, the configuration needs to be computed accordingly.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// The position of configuration bounds were calculated in screen space because that is
// easier to resolve the relative position in parent container. However, if the activity is
@@ -8946,17 +8966,18 @@
* to compute the stable bounds.
* @param outStableBounds will store the stable bounds, which are the bounds with insets
* applied, if orientation is not respected when insets are applied.
- * Otherwise outStableBounds will be empty. Stable bounds should be used
- * to compute letterboxed bounds if orientation is not respected when
- * insets are applied.
+ * Stable bounds should be used to compute letterboxed bounds if
+ * orientation is not respected when insets are applied.
+ * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
+ * decor insets applied, like display cutout and nav bar.
*/
- private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+ private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds,
+ Rect outNonDecorBounds) {
outStableBounds.setEmpty();
if (mDisplayContent == null) {
return true;
}
- if (mWmService.mFlags.mInsetsDecoupledConfiguration
- && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds) {
// No insets should be considered any more.
return true;
}
@@ -8973,8 +8994,9 @@
? getFixedRotationTransformDisplayInfo()
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
- task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+ task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -8984,9 +9006,6 @@
// have the desired orientation.
final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
- if (orientationRespectedWithInsets) {
- outStableBounds.setEmpty();
- }
return orientationRespectedWithInsets;
}
@@ -9012,9 +9031,10 @@
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect stableBounds = new Rect();
+ final Rect outNonDecorBounds = mTmpBounds;
// If orientation is respected when insets are applied, then stableBounds will be empty.
boolean orientationRespectedWithInsets =
- orientationRespectedWithInsets(parentBounds, stableBounds);
+ orientationRespectedWithInsets(parentBounds, stableBounds, outNonDecorBounds);
if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant(
getOverrideOrientation())) {
// No need to letterbox because of fixed orientation. Display will handle
@@ -9031,7 +9051,10 @@
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
- final int parentOrientation = newParentConfig.orientation;
+ final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ final int parentOrientation = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? stableBoundsOrientation : newParentConfig.orientation;
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
@@ -9046,7 +9069,8 @@
}
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
- if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+ if (compatDisplayInsets != null
+ && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
// have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9054,10 +9078,12 @@
return;
}
+ final Rect parentAppBounds = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
final Rect parentBoundsWithInsets = orientationRespectedWithInsets
- ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+ ? parentAppBounds : stableBounds;
final Rect containingBounds = new Rect();
final Rect containingBoundsWithInsets = new Rect();
// Need to shrink the containing bounds into a square because the parent orientation
@@ -9134,8 +9160,8 @@
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
- getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
- newParentConfig, compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
@@ -9176,8 +9202,9 @@
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getFixedRotationTransformDisplayInfo());
+ mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
}
}
@@ -9241,8 +9268,8 @@
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = computeScreenLayout(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -10741,10 +10768,10 @@
/** Whether the {@link Task} windowingMode represents a floating window*/
final boolean mIsFloating;
/**
- * Whether is letterboxed because of fixed orientation when the unresizable activity is
- * first shown.
+ * Whether is letterboxed because of fixed orientation or aspect ratio when
+ * the unresizable activity is first shown.
*/
- final boolean mIsInFixedOrientationLetterbox;
+ final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
* is used to compute the appBounds.
@@ -10759,7 +10786,7 @@
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
- @Nullable Rect fixedOrientationBounds) {
+ @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
mOriginalRotation = display.getRotation();
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10774,22 +10801,22 @@
mNonDecorInsets[rotation] = emptyRect;
mStableInsets[rotation] = emptyRect;
}
- mIsInFixedOrientationLetterbox = false;
+ mIsInFixedOrientationOrAspectRatioLetterbox = false;
return;
}
final Task task = container.getTask();
- mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+ mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
// Store the bounds of the Task for the non-resizable activity to use in size compat
// mode so that the activity will not be resized regardless the windowing mode it is
// currently in.
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds instead of task bounds since the activity will be displayed
- // within these even if it is in size compat mode.
- final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
- ? fixedOrientationBounds
+ // When an activity needs to be letterboxed because of fixed orientation or aspect
+ // ratio, use resolved bounds instead of task bounds since the activity will be
+ // displayed within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+ ? letterboxedContainerBounds
: task != null ? task.getBounds() : display.getBounds();
final int filledContainerRotation = task != null
? task.getConfiguration().windowConfiguration.getRotation()
@@ -10811,8 +10838,13 @@
final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
final DisplayPolicy.DecorInsets.Info decorInfo =
policy.getDecorInsetsInfo(rotation, dw, dh);
- mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
- mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ if (useOverrideInsets) {
+ mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+ } else {
+ mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+ }
if (unfilledContainerBounds == null) {
continue;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2c39c58..08baf3b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2524,7 +2524,9 @@
// If the caller has asked not to resume at this point, we make note
// of this in the record so that we can skip it when trying to find
// the top running activity.
- if (!r.showToCurrentUser() || mLaunchTaskBehind) {
+ final boolean canShowActivity = r.showToCurrentUser();
+ if (!canShowActivity) Slog.w(TAG, "Can't resume non-current user r=" + r);
+ if (!canShowActivity || mLaunchTaskBehind) {
r.delayedResume = true;
mDoResume = false;
} else {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e0d4f9..84dadab 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -41,7 +41,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -986,19 +985,6 @@
+ " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
attrs.getFitInsetsTypes()));
}
- if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && attrs.layoutInDisplayCutoutMode
- != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
- // A non-translucent main window of the app enforced to go edge-to-edge
- // isn't allowed to fit display cutout, or it will cause software bezels.
- throw new IllegalArgumentException("Illegal attributes: Main window of "
- + win.mActivityRecord.getName() + " that isn't translucent and"
- + " targets SDK level " + win.mActivityRecord.mTargetSdk
- + " (>= 35) trying to specify layoutInDisplayCutoutMode as '"
- + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString(
- attrs.layoutInDisplayCutoutMode)
- + "' instead of 'always'");
- }
}
break;
}
@@ -1939,6 +1925,11 @@
*/
final Rect mOverrideConfigInsets = new Rect();
+ /**
+ * Override value of mNonDecorInsets for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorInsets = new Rect();
+
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1954,6 +1945,11 @@
*/
final Rect mOverrideConfigFrame = new Rect();
+ /**
+ * Override value of mNonDecorFrame for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorFrame = new Rect();
+
private boolean mNeedUpdate = true;
InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1973,17 +1969,26 @@
? configInsets
: insetsState.calculateInsets(displayFrame,
dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
+ final Insets overrideDecorInsets = dc.mWmService.mDecorTypes
+ == dc.mWmService.mOverrideDecorTypes
+ ? decor
+ : insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mOverrideDecorTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
overrideConfigInsets.right, overrideConfigInsets.bottom);
+ mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top,
+ overrideDecorInsets.right, overrideDecorInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
mOverrideConfigFrame.set(displayFrame);
mOverrideConfigFrame.inset(mOverrideConfigInsets);
+ mOverrideNonDecorFrame.set(displayFrame);
+ mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1992,9 +1997,11 @@
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
mOverrideConfigInsets.set(other.mOverrideConfigInsets);
+ mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
mOverrideConfigFrame.set(other.mOverrideConfigFrame);
+ mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame);
mNeedUpdate = false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f220c9d..cb5ad91 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1308,7 +1308,8 @@
}
final boolean shouldShowLetterboxUi =
- (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+ (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+ || mActivityRecord.isVisibleRequested())
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
@@ -1320,12 +1321,6 @@
return shouldShowLetterboxUi;
}
- @VisibleForTesting
- boolean isSurfaceVisible(WindowState mainWindow) {
- return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
- || mActivityRecord.isVisibleRequested());
- }
-
private Color getLetterboxBackgroundColor() {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || w.isLetterboxedForDisplayCutout()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d79d113..390a7cf 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4748,14 +4748,6 @@
// transferring the transform on the leash to the task, reset this state once we're
// moving out of pip
setCanAffectSystemUiFlags(true);
- // Turn on userLeaveHint so other app can enter PiP mode.
- mTaskSupervisor.mUserLeaving = true;
- // Allow entering PiP from current top most activity when we are leaving PiP.
- final Task topFocused = mRootWindowContainer.getTopDisplayFocusedRootTask();
- if (topFocused != null) {
- final ActivityRecord ar = topFocused.getTopResumedActivity();
- enableEnterPipOnTaskSwitch(ar, null /* toFrontTask */, ar, null /* opts */);
- }
mRootWindowContainer.notifyActivityPipModeChanged(this, null);
}
if (likelyResolvedMode == WINDOWING_MODE_PINNED) {
@@ -4813,6 +4805,14 @@
topActivity.getSyncTransaction());
}
lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+ // If the reparent is not included in transition, make sure the visibility of
+ // task is still updated by core. Otherwise if the task is collected (e.g.
+ // rotation change) after leaving this scope, the visibility operation will be
+ // put in sync transaction, then it is not synced with reparent.
+ if (com.android.window.flags.Flags.removePrepareSurfaceInPlacement()
+ && lastParentBeforePip.mSyncState == SYNC_STATE_NONE) {
+ lastParentBeforePip.prepareSurfaces();
+ }
}
if (isPip2ExperimentEnabled) {
super.setWindowingMode(windowingMode);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 218fb7f..fc85af5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -906,8 +906,12 @@
return mForceTranslucent;
}
- void setForceTranslucent(boolean set) {
+ boolean setForceTranslucent(boolean set) {
+ if (mForceTranslucent == set) {
+ return false;
+ }
mForceTranslucent = set;
+ return true;
}
boolean isLeafTaskFragment() {
@@ -2188,38 +2192,20 @@
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
+ static class ConfigOverrideHint {
+ @Nullable DisplayInfo mTmpOverrideDisplayInfo;
+ @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+ boolean mUseLegacyInsetsForStableBounds;
+ }
+
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
- if (overrideDisplayInfo != null) {
- // Make sure the screen related configs can be computed by the provided display info.
- inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
- if (compatInsets != null) {
- // Make sure the app bounds can be computed by the compat insets.
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- compatInsets);
+ computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */);
}
/**
* Forces the app bounds related configuration can be computed by
- * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
- * ActivityRecord.CompatDisplayInsets)}.
+ * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}.
*/
private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2239,8 +2225,24 @@
* just be inherited from the parent configuration.
**/
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
+ DisplayInfo overrideDisplayInfo = null;
+ ActivityRecord.CompatDisplayInsets compatInsets = null;
+ boolean useLegacyInsetsForStableBounds = false;
+ if (overrideHint != null) {
+ overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
+ compatInsets = overrideHint.mTmpCompatInsets;
+ useLegacyInsetsForStableBounds = overrideHint.mUseLegacyInsetsForStableBounds;
+ if (overrideDisplayInfo != null) {
+ // Make sure the screen related configs can be computed by the provided
+ // display info.
+ inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+ }
+ if (overrideDisplayInfo != null || compatInsets != null) {
+ // Make sure the app bounds can be computed by the compat insets.
+ invalidateAppBoundsConfig(inOutConfig);
+ }
+ }
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2309,7 +2311,8 @@
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+ useLegacyInsetsForStableBounds);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2407,9 +2410,11 @@
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
+ * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+ * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
+ DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2420,8 +2425,13 @@
final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
- intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ if (!useLegacyInsetsForStableBounds) {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+ } else {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 143605a..1496ae0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -570,6 +570,8 @@
final int mOverrideConfigTypes;
+ final int mOverrideDecorTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1255,10 +1257,13 @@
if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
// If the global new behavior is not there, but the partial decouple flag is on.
mOverrideConfigTypes = 0;
+ mOverrideDecorTypes = 0;
} else {
mOverrideConfigTypes =
WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
+ mOverrideDecorTypes = WindowInsets.Type.displayCutout()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8c9317a..4180475 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -609,6 +609,11 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
+ final boolean shouldDeferTransitionReady = transition != null && !t.isEmpty()
+ && (transition.isCollecting() || Flags.alwaysDeferTransitionWhenApplyWct());
+ if (shouldDeferTransitionReady) {
+ transition.deferTransitionReady();
+ }
try {
final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
if (transition != null) {
@@ -761,6 +766,9 @@
mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
} finally {
+ if (shouldDeferTransitionReady) {
+ transition.continueTransitionReady();
+ }
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.continueWindowLayout();
}
@@ -842,8 +850,9 @@
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
- tr.setForceTranslucent(c.getForceTranslucent());
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ if (tr.setForceTranslucent(c.getForceTranslucent())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -964,8 +973,9 @@
}
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
- taskFragment.setForceTranslucent(c.getForceTranslucent());
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ if (taskFragment.setForceTranslucent(c.getForceTranslucent())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
effects |= applyChanges(taskFragment, c);
@@ -1100,14 +1110,8 @@
launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
final SafeActivityOptions safeOptions =
SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
- if (transition != null) {
- transition.deferTransitionReady();
- }
waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
caller.mPid, caller.mUid, taskId, safeOptions));
- if (transition != null) {
- transition.continueTransitionReady();
- }
break;
}
case HIERARCHY_OP_TYPE_REORDER:
@@ -1185,17 +1189,11 @@
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
}
final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
- if (transition != null) {
- transition.deferTransitionReady();
- }
int res = waitAsyncStart(() -> mService.mAmInternal.sendIntentSender(
hop.getPendingIntent().getTarget(),
hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
null /* requiredPermission */, options));
- if (transition != null) {
- transition.continueTransitionReady();
- }
if (ActivityManager.isStartResultSuccessful(res)) {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -1568,26 +1566,32 @@
case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
- task.mChildren.remove(taskFragment);
- task.mChildren.add(0, taskFragment);
- if (!taskFragment.hasChild()) {
- // Ensure that the child layers are updated if the TaskFragment is empty.
- task.assignChildLayers();
+ if (task.mChildren.peekFirst() != taskFragment) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(0, taskFragment);
+ if (!taskFragment.hasChild()) {
+ // Ensure that the child layers are updated if the TaskFragment is
+ // empty.
+ task.assignChildLayers();
+ }
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
- task.mChildren.remove(taskFragment);
- task.mChildren.add(taskFragment);
- if (!taskFragment.hasChild()) {
- // Ensure that the child layers are updated if the TaskFragment is empty.
- task.assignChildLayers();
+ if (task.mChildren.peekLast() != taskFragment) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(taskFragment);
+ if (!taskFragment.hasChild()) {
+ // Ensure that the child layers are updated if the TaskFragment is
+ // empty.
+ task.assignChildLayers();
+ }
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index b999305f..736b051 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,12 +1,8 @@
-# Display
-per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
-
# Input
per-file com_android_server_input_* = file:/INPUT_OWNERS
# Power
-per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
-per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+per-file com_android_server_HardwarePropertiesManagerService.cpp = file:/services/core/java/com/android/server/power/OWNERS
# BatteryStats
per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
@@ -16,6 +12,7 @@
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 8ca5333..da95666 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -487,7 +487,6 @@
timer_id_t front = headTimerId();
auto found = running_.find(key);
if (found != running_.end()) running_.erase(found);
- if (front != headTimerId()) restartLocked();
}
// Remove every timer associated with the service.
@@ -501,7 +500,6 @@
i++;
}
}
- if (front != headTimerId()) restartLocked();
}
// Return the number of timers still running.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb63757..7e083ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -107,6 +107,8 @@
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
+import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
@@ -886,10 +888,6 @@
"enable_permission_based_access";
private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
- // TODO(b/266831522) remove the flag after rollout.
- private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
- private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
-
private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
/**
@@ -3689,26 +3687,6 @@
mDevicePolicyEngine.handleStartUser(userId);
}
- void pushUserControlDisabledPackagesLocked(int userId) {
- final int targetUserId;
- final ActiveAdmin owner;
- if (getDeviceOwnerUserIdUncheckedLocked() == userId) {
- owner = getDeviceOwnerAdminLocked();
- targetUserId = UserHandle.USER_ALL;
- } else {
- owner = getProfileOwnerAdminLocked(userId);
- targetUserId = userId;
- }
-
- List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
- ? null : owner.protectedPackages;
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
- targetUserId, protectedPackages));
- mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
- targetUserId);
- }
-
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
mDevicePolicyEngine.handleUnlockUser(userId);
@@ -3957,7 +3935,7 @@
if (policy.mPasswordOwner == oldAdminUid) {
policy.mPasswordOwner = adminToTransfer.getUid();
}
-
+ transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
saveSettingsLocked(userHandle);
sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
null, null);
@@ -15913,14 +15891,6 @@
}
@Override
- public boolean isApplicationExemptionsFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- APPLICATION_EXEMPTIONS_FLAG,
- DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
- }
-
- @Override
public List<Bundle> getApplicationRestrictionsPerAdminForUser(
String packageName, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId
@@ -19501,6 +19471,21 @@
.write();
}
+ private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+ if (Flags.esimManagementEnabled()) {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+ }
+ }
+ }
+ }
+
private void prepareTransfer(ComponentName admin, ComponentName target,
PersistableBundle bundle, int callingUserId, String adminType) {
saveTransferOwnershipBundleLocked(bundle, callingUserId);
@@ -20378,34 +20363,47 @@
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
final CallerIdentity caller = getCallerIdentity(callerPackage);
- final ApplicationInfo packageInfo;
- packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final AppOpsManager appOpsMgr = mInjector.getAppOpsManager();
+ final ApplicationInfo appInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final int uid = appInfo.uid;
- for (Map.Entry<Integer, String> entry :
- APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
- int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
- entry.getValue(), packageInfo.uid, packageInfo.packageName);
- int newMode = ArrayUtils.contains(exemptions, entry.getKey())
- ? MODE_ALLOWED : MODE_DEFAULT;
- mInjector.binderWithCleanCallingIdentity(() -> {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.forEach((exemption, appOp) -> {
+ int currentMode = appOpsMgr.unsafeCheckOpNoThrow(appOp, uid, packageName);
+ int newMode = ArrayUtils.contains(exemptions, exemption)
+ ? MODE_ALLOWED : MODE_DEFAULT;
if (currentMode != newMode) {
- mInjector.getAppOpsManager()
- .setMode(entry.getValue(),
- packageInfo.uid,
- packageName,
- newMode);
+ appOpsMgr.setMode(appOp, uid, packageName, newMode);
+
+ // If the user has already disabled background usage for the package, it won't
+ // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
+ // code below grants that app op, and once the exemption is in place, the user
+ // won't be able to disable background usage anymore.
+ if (Flags.powerExemptionBgUsageFix()
+ && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
+ && newMode == MODE_ALLOWED) {
+ setBgUsageAppOp(appOpsMgr, appInfo);
+ }
}
});
- }
+ });
+
String[] appOpExemptions = new String[exemptions.length];
for (int i = 0; i < exemptions.length; i++) {
appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]);
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
- .setAdmin(caller.getPackageName())
- .setStrings(packageName, appOpExemptions)
- .write();
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
+ .setAdmin(caller.getPackageName())
+ .setStrings(packageName, appOpExemptions)
+ .write();
+ }
+
+ static void setBgUsageAppOp(AppOpsManager appOpsMgr, ApplicationInfo appInfo) {
+ appOpsMgr.setMode(OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ if (appInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ appOpsMgr.setMode(OP_RUN_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ }
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 42ac998..d02cfee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -357,7 +357,8 @@
@Override
boolean shouldWrite() {
- return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
+ return Flags.alwaysPersistDo()
+ || (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
|| (mSystemUpdateInfo != null);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e713a82..7a9fa0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -163,8 +163,7 @@
new NoArgsPolicyKey(
DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
new StringSetUnion(),
- (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
- PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+ PolicyEnforcerCallbacks::setUserControlDisabledPackages,
new StringSetPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index a7adc5b..a0d9be54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -29,6 +30,7 @@
import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -183,15 +186,31 @@
}
static boolean setUserControlDisabledPackages(
- @Nullable Set<String> packages, int userId) {
+ @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
- LocalServices.getService(PackageManagerInternal.class)
- .setOwnerProtectedPackages(
- userId,
- packages == null ? null : packages.stream().toList());
+ PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ pmi.setOwnerProtectedPackages(userId,
+ packages == null ? null : packages.stream().toList());
LocalServices.getService(UsageStatsManagerInternal.class)
- .setAdminProtectedPackages(
+ .setAdminProtectedPackages(
packages == null ? null : new ArraySet<>(packages), userId);
+
+ if (Flags.disallowUserControlBgUsageFix()) {
+ if (packages == null) {
+ return;
+ }
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ for (var pkg : packages) {
+ final var appInfo = pmi.getApplicationInfo(pkg,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ Process.myUid(), userId);
+ if (appInfo != null) {
+ DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
+ }
+ }
+ }
});
return true;
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 3bce9b5..0da17e1 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -84,6 +84,7 @@
],
srcs: [
"src/com/android/server/inputmethod/**/ClientControllerTest.java",
+ "src/com/android/server/inputmethod/**/UserDataRepositoryTest.java",
],
auto_gen_config: true,
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
new file mode 100644
index 0000000..a15b170
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.UserInfo;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class UserDataRepositoryTest {
+
+ private static final int ANY_USER_ID = 1;
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true).build();
+
+ @Mock
+ private UserManagerInternal mMockUserManagerInternal;
+
+ private Handler mHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Test
+ public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
+ // Create UserDataRepository and capture the user lifecycle listener
+ final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
+ final var listener = captor.getValue();
+
+ // Assert that UserDataRepository is empty and then call onUserCreated
+ assertThat(collectUserData(repository)).isEmpty();
+ final var userInfo = new UserInfo();
+ userInfo.id = ANY_USER_ID;
+ listener.onUserCreated(userInfo, /* unused token */ new Object());
+ waitForIdle();
+
+ // Assert UserDataRepository contains the expected UserData
+ final var allUserData = collectUserData(repository);
+ assertThat(allUserData).hasSize(1);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(userInfo.id);
+ }
+
+ @Test
+ public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
+ // Create UserDataRepository and capture the user lifecycle listener
+ final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
+ final var listener = captor.getValue();
+
+ // Add one UserData ...
+ final var userInfo = new UserInfo();
+ userInfo.id = ANY_USER_ID;
+ listener.onUserCreated(userInfo, /* unused token */ new Object());
+ waitForIdle();
+ // ... and then call onUserRemoved
+ assertThat(collectUserData(repository)).hasSize(1);
+ listener.onUserRemoved(userInfo);
+ waitForIdle();
+
+ // Assert UserDataRepository is now empty
+ assertThat(collectUserData(repository)).isEmpty();
+ }
+
+ @Test
+ public void testGetOrCreate() {
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+
+ synchronized (ImfLock.class) {
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
+ }
+
+ final var allUserData = collectUserData(repository);
+ assertThat(allUserData).hasSize(1);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+ }
+
+ private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
+ final var collected = new ArrayList<UserDataRepository.UserData>();
+ synchronized (ImfLock.class) {
+ repository.forAllUserData(userData -> collected.add(userData));
+ }
+ return collected;
+ }
+
+ private void waitForIdle() {
+ final var done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 7d3a110..c1f4fee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -321,8 +321,7 @@
app1PackageName); // packageName
ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
- mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
- false);
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
assertEquals(list.size(), 2);
@@ -336,7 +335,7 @@
app1ProcessName, // processName
ApplicationStartInfo.START_REASON_SERVICE, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 5: Create an instance of app1 with a different user started for a broadcast
@@ -350,7 +349,7 @@
app1PackageName); // packageName
mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
- null, true /* isColdStart */);
+ buildIntent(COMPONENT), false /* isAlarm */);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -395,7 +394,7 @@
app2PackageName); // packageName
mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
- app, false);
+ app);
list.clear();
mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -409,7 +408,7 @@
app2ProcessName, // processName
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 8: Save and load again
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281da..5861917 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -112,6 +112,9 @@
@Mock
ProcessList mProcessList;
+ @Mock
+ AppStartInfoTracker mAppStartInfoTracker;
+
Context mContext;
ActivityManagerService mAms;
BroadcastConstants mConstants;
@@ -172,6 +175,8 @@
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+ doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
}
public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 97ae0bd..13ba1e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2393,6 +2393,20 @@
assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
}
+ @Test
+ public void testBroadcastAppStartInfoReported() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent timezone = new Intent(Intent.ACTION_TIME_TICK);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+
+ verify(mAppStartInfoTracker, times(1)).handleProcessBroadcastStart(anyLong(), any(), any(),
+ anyBoolean());
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 4c7a8fe..9590783 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -205,8 +205,6 @@
new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats")));
setFieldValue(ActivityManagerService.class, sService, "mBackupTargets",
mock(SparseArray.class));
- setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
- mock(OomAdjProfiler.class));
setFieldValue(ActivityManagerService.class, sService, "mUserController",
mock(UserController.class));
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907..584fd62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -75,6 +76,9 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -98,6 +102,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -154,6 +159,10 @@
@Mock
private UsageStatsManagerInternal mUsageStatsManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private JobStore mJobStore;
@Before
@@ -1978,7 +1987,7 @@
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2030,7 @@
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2176,74 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_COUNT_QUOTA_FIX)
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+ setDischarging();
+
+ JobStatus jobRunning = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+ JobStatus jobPending = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+ setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+ long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+ final ExecutionStats stats;
+ synchronized (mQuotaController.mLock) {
+ stats = mQuotaController.getExecutionStatsLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ assertTrue(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.jobCountLimit);
+ assertEquals(9, stats.bgJobCountInWindow);
+ }
+
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+ trackJobs(jobRunning, jobPending);
+ // UID in the background.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobRunning);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Wait for some extra time to allow for job processing.
+ ArraySet<JobStatus> expected = new ArraySet<>();
+ expected.add(jobPending);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(eq(expected));
+
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+ assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobRunning.isReady());
+ assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+ assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobPending.isReady());
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+ }
+
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+ }
+
+ @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4728,7 @@
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6618,7 +6695,7 @@
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 4535ece..8d0b279 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -191,6 +191,8 @@
when(mContext.checkCallingOrSelfPermission(
eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
PackageManager.PERMISSION_DENIED);
+ when(mContext.createPackageContextAsUser(
+ eq(INSTALLER_PACKAGE), anyInt(), eq(UserHandle.CURRENT))).thenReturn(mContext);
when(mAppOpsManager.checkOp(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index f387238..12a7038 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -52,6 +52,7 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.runner",
+ "compatibility-device-util-axt",
"services.core",
],
test_suites: [
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
index b36c9bd..e86108d 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -15,98 +15,144 @@
*/
package com.android.server.selinux;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
import static com.google.common.truth.Truth.assertThat;
+import android.provider.DeviceConfig;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.regex.Matcher;
+
@RunWith(AndroidJUnit4.class)
public class SelinuxAuditLogsBuilderTest {
- private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ private static final String TEST_DOMAIN = "test_domain";
+
+ private SelinuxAuditLogBuilder mAuditLogBuilder;
+ private Matcher mScontextMatcher;
+ private Matcher mTcontextMatcher;
+ private Matcher mPathMatcher;
+
+ @Before
+ public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ mScontextMatcher = mAuditLogBuilder.mScontextMatcher;
+ mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher;
+ mPathMatcher = mAuditLogBuilder.mPathMatcher;
+ }
+
+ @After
+ public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
+ }
@Test
public void testMatcher_scontext() {
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(mScontextMatcher.group("scategories")).isNull();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(toCategories(mScontextMatcher.group("scategories")))
.isEqualTo(new int[] {123, 456});
- assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse();
}
@Test
public void testMatcher_tcontext() {
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
- assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type");
+ assertThat(mTcontextMatcher.group("tcategories")).isNull();
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
- assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2");
+ assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666});
- assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
- assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse();
}
@Test
public void testMatcher_path() {
- assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
- assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
- assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+ assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data");
+ assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
+ assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
- assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
- assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_scontextDefaultConfig() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.clearLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN));
+
+ Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher;
+
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isFalse();
+ assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
}
@Test
public void testSelinuxAuditLogsBuilder_noOptionals() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0"
+ " tclass=c");
- assertAuditLog(
- mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+ assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c");
mAuditLogBuilder.reset(
"tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
- + " scontext=u:r:sdk_sandbox_audit:s0");
+ + " scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0");
assertAuditLog(
- mAuditLogBuilder.build(),
- true,
- new String[] {"p2"},
- "sdk_sandbox_audit",
- "t2",
- "c2");
+ mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2");
}
@Test
public void testSelinuxAuditLogsBuilder_withCategories() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123"
+ " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"t",
new int[] {456, 666},
@@ -118,13 +164,15 @@
@Test
public void testSelinuxAuditLogsBuilder_withPath() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -136,13 +184,15 @@
@Test
public void testSelinuxAuditLogsBuilder_withPermissive() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0"
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -151,13 +201,15 @@
false);
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c"
+ " permissive=1");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -166,6 +218,40 @@
true);
}
+ @Test
+ public void testSelinuxAuditLogsBuilder_wrongConfig() {
+ String notARegexDomain = "not]a[regex";
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ notARegexDomain));
+ SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder();
+
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ }
+
private void assertAuditLog(
SelinuxAuditLog auditLog,
boolean granted,
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index 4a70ad3..b6ccf5e 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.selinux;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
+import android.provider.DeviceConfig;
import android.util.EventLog;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,6 +52,7 @@
// Fake tag to use for testing
private static final int ANSWER_TAG = 42;
+ private static final String TEST_DOMAIN = "test_domain";
private final MockClock mClock = new MockClock();
@@ -64,6 +67,14 @@
@Before
public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mSelinuxAutidLogsCollector.setStopRequested(false);
// move the clock forward for the limiters.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
// Ignore what was written in the event logs by previous tests.
@@ -74,13 +85,14 @@
@After
public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
mMockitoSession.finishMocking();
}
@Test
- public void testWriteSdkSandboxAuditLogs() {
- writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+ public void testWriteAuditLogs() {
+ writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -91,7 +103,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
true,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -104,7 +116,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype1",
null,
@@ -114,9 +126,9 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_multiplePerms() {
- writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_multiplePerms() {
+ writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -127,7 +139,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1", "perm2"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -140,7 +152,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm3", "perm4"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -150,11 +162,11 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPaths() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+ public void testWriteAuditLogs_withPaths() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -165,7 +177,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -178,7 +190,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -191,7 +203,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -204,7 +216,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -214,23 +226,14 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withCategories() {
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+ public void testWriteAuditLogs_withCategories() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass");
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
- new int[] {123, 456},
- "ttype",
- null,
- "tclass");
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
- writeTestLog(
- "denied",
- "perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -245,7 +248,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
null,
@@ -258,7 +261,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
null,
@@ -271,7 +274,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
new int[] {666},
@@ -284,7 +287,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -294,11 +297,11 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+ public void testWriteAuditLogs_withPathAndCategories() {
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -314,7 +317,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -324,10 +327,10 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_permissive() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+ public void testWriteAuditLogs_permissive() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -338,7 +341,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -352,7 +355,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -362,7 +365,7 @@
}
@Test
- public void testNotWriteAuditLogs_notSdkSandbox() {
+ public void testNotWriteAuditLogs_notTestDomain() {
writeTestLog("denied", "perm", "stype", "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -385,15 +388,15 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_upToQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_upToQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -415,14 +418,14 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_resetQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_resetQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
@@ -441,11 +444,11 @@
anyBoolean()),
times(5));
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// move the clock forward to reset the quota limiter.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -468,16 +471,16 @@
@Test
public void testNotWriteAuditLogs_stopRequested() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isFalse();
verify(
@@ -495,7 +498,7 @@
anyBoolean()),
never());
- mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ mSelinuxAutidLogsCollector.setStopRequested(false);
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
verify(
@@ -516,8 +519,8 @@
@Test
public void testAuditLogs_resumeJobDoesNotExceedLimit() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
new file mode 100644
index 0000000..2aea8a0
--- /dev/null
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsJobTest {
+
+ private final JobService mJobService = mock(JobService.class);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector =
+ mock(SelinuxAuditLogsCollector.class);
+ private final JobParameters mParams = createJobParameters(666);
+ private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector);
+
+ @Before
+ public void setUp() {
+ mAuditLogsCollector.mStopRequested = new AtomicBoolean();
+ }
+
+ @Test
+ public void testFinishSuccessfully() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterrupt() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterruptAndResume() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testRequestStop() throws InterruptedException {
+ Semaphore isRunning = new Semaphore(0);
+ Semaphore stopRequested = new Semaphore(0);
+ AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
+
+ // Set up a logs collector that runs in a worker thread until a stop is requested.
+ when(mAuditLogsCollector.collect(anyInt()))
+ .thenAnswer(
+ invocation -> {
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse();
+ isRunning.release();
+ stopRequested.acquire();
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue();
+ return true;
+ });
+ Thread jobThread =
+ new Thread(
+ () -> {
+ mAuditLogsJob.start(mJobService, mParams);
+ });
+ jobThread.setUncaughtExceptionHandler(
+ (thread, exception) -> uncaughtException.set(exception));
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ jobThread.start();
+
+ // Wait until the worker thread is running.
+ isRunning.acquire();
+ assertThat(mAuditLogsJob.isRunning()).isTrue();
+
+ // Request for the worker thread to stop, and wait to verify.
+ mAuditLogsJob.requestStop();
+ stopRequested.release();
+ jobThread.join();
+ assertThat(uncaughtException.get()).isNull();
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ private static JobParameters createJobParameters(int jobId) {
+ JobParameters jobParameters = mock(JobParameters.class);
+ when(jobParameters.getJobId()).thenReturn(jobId);
+ return jobParameters;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 5a17851..cb4fc75 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -26,6 +26,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +69,10 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -110,6 +114,7 @@
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -197,31 +202,34 @@
private AccessibilityManagerService mA11yms;
private TestableLooper mTestableLooper;
private Handler mHandler;
+ private FakePermissionEnforcer mFakePermissionEnforcer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mHandler = new Handler(mTestableLooper.getLooper());
-
+ mFakePermissionEnforcer = new FakePermissionEnforcer();
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(PermissionEnforcer.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
LocalServices.addService(
UserManagerInternal.class, mMockUserManagerInternal);
- LocalServices.addService(
- StatusBarManagerInternal.class, mStatusBarManagerInternal);
+ LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
mInputFilter = Mockito.mock(FakeInputFilter.class);
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.isFullScreenMagnificationControllerInitialized())
+ .thenReturn(true);
when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
@@ -248,7 +256,8 @@
mMockA11yDisplayListener,
mMockMagnificationController,
mInputFilter,
- mProxyManager);
+ mProxyManager,
+ mFakePermissionEnforcer);
final AccessibilityUserState userState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
@@ -305,9 +314,7 @@
@SmallTest
@Test
public void testRegisterSystemActionWithoutPermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID));
verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
@@ -316,15 +323,14 @@
@SmallTest
@Test
public void testRegisterSystemAction() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
}
@Test
public void testUnregisterSystemActionWithoutPermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.unregisterSystemAction(ACTION_ID));
verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
@@ -333,6 +339,7 @@
@SmallTest
@Test
public void testUnregisterSystemAction() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.unregisterSystemAction(ACTION_ID);
verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
}
@@ -353,6 +360,7 @@
@SmallTest
@Test
public void testRegisterProxy() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), anyInt(),
@@ -364,6 +372,7 @@
@SmallTest
@Test
public void testRegisterProxyWithoutA11yPermissionOrRole() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
doThrow(SecurityException.class).when(mMockSecurityPolicy)
.checkForAccessibilityPermissionOrRole();
@@ -376,9 +385,7 @@
@SmallTest
@Test
public void testRegisterProxyWithoutDevicePermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
@@ -397,6 +404,7 @@
@SmallTest
@Test
public void testRegisterProxyForInvalidDisplay() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
@@ -406,6 +414,7 @@
@SmallTest
@Test
public void testUnRegisterProxyWithPermission() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
@@ -427,9 +436,7 @@
@SmallTest
@Test
public void testUnRegisterProxyWithoutDevicePermission() {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(SecurityException.class,
() -> mA11yms.unregisterProxyForDisplay(TEST_DISPLAY));
verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
@@ -568,6 +575,17 @@
verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
}
+ @Test
+ @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
+ throws RemoteException {
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mA11yms.setMagnificationConnection(null);
+
+ verify(mMockFullScreenMagnificationController, atLeastOnce()).reset(
+ /* displayId= */ anyInt(), /* animate= */ anyBoolean());
+ }
+
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
@@ -774,7 +792,7 @@
public void testPerformAccessibilityShortcut_hearingAids_startActivityWithExpectedComponent() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
userState.mAccessibilityShortcutKeyTargets.add(
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -792,7 +810,7 @@
public void testPerformAccessibilityShortcut_hearingAids_sendExpectedBroadcast() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
userState.mAccessibilityShortcutKeyTargets.add(
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -906,7 +924,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME);
assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue();
@@ -914,7 +932,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME);
final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
new ComponentName("package_b", "class_b"));
@@ -928,7 +946,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
new ComponentName("package_a", "class_a"));
final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
@@ -949,7 +967,7 @@
@Test
@EnableFlags(FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
new ComponentName("package_a", "class_a"),
/* isSystemApp= */ true, /* isAlwaysOnService= */ false);
@@ -989,7 +1007,10 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1008,13 +1029,16 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
AccessibilityShortcutController.DialogStatus.NOT_SHOWN
);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1035,6 +1059,9 @@
@Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
@@ -1052,7 +1079,10 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1071,7 +1101,7 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_userConfigureSmallMenuSize_menuSizeNotChanged() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
@@ -1095,7 +1125,10 @@
@Test
public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1115,6 +1148,9 @@
@Test
public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
mA11yms.enableShortcutsForTargets(
@@ -1134,7 +1170,7 @@
@Test
public void enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1154,6 +1190,9 @@
@Test
public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
AccessibilityUtils.setAccessibilityServiceState(
mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1174,7 +1213,10 @@
@Test
public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1193,6 +1235,9 @@
@Test
public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1211,7 +1256,10 @@
@Test
public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1230,6 +1278,9 @@
@Test
public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1249,7 +1300,10 @@
@Test
public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1268,6 +1322,9 @@
@Test
public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1278,15 +1335,19 @@
mTestableLooper.processAllMessages();
assertThat(
- ShortcutUtils.isComponentIdExistingInSettings(
- mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
- TARGET_STANDARD_A11Y_SERVICE.flattenToString())
- ).isFalse();
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext,
+ ShortcutConstants.UserShortcutType.HARDWARE,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString()))
+ .isFalse();
}
@Test
public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1311,6 +1372,9 @@
@Test
public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableQuickSettings_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1343,7 +1407,7 @@
@Test
public void getA11yFeatureToTileMap() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
Bundle bundle = mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked());
@@ -1368,9 +1432,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
- mTestableContext.getTestablePermissions().setPermission(
- Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.revoke(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.notifyQuickSettingsTilesChanged(
@@ -1382,7 +1445,7 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
- mockStatusBarServiceGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mTestableContext.getTestablePermissions().setPermission(
Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
@@ -1396,8 +1459,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
List<ComponentName> tiles = List.of(
AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
@@ -1433,8 +1496,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
ComponentName tile = new ComponentName(
TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
@@ -1451,8 +1514,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
final AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mAccessibilityButtonTargets.clear();
@@ -1473,8 +1536,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
List<ComponentName> tiles = List.of(
AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
@@ -1601,16 +1664,6 @@
return lockState;
}
- private void mockManageAccessibilityGranted(TestableContext context) {
- context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
- PackageManager.PERMISSION_GRANTED);
- }
-
- private void mockStatusBarServiceGranted(TestableContext context) {
- context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
- PackageManager.PERMISSION_GRANTED);
- }
-
private void assertStartActivityWithExpectedComponentName(Context mockContext,
String componentName) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
@@ -1695,4 +1748,8 @@
return mBroadcastReceivers;
}
}
+
+ private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
+ return service.getCurrentUserIdLocked() == context.getUserId();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f3cd0d6..7b71f85 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -20,6 +20,7 @@
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -139,6 +140,8 @@
private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+ private boolean mMockMagnificationConnectionState;
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -175,6 +178,8 @@
mScaleProvider = new MagnificationScaleProvider(mMockContext);
+ // Assume the connection is established by default
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mMockControllerCtx,
@@ -184,7 +189,8 @@
() -> mMockThumbnail,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mMockTimeAnimator);
+ () -> mMockTimeAnimator,
+ () -> mMockMagnificationConnectionState);
}
@After
@@ -196,7 +202,6 @@
CURRENT_USER_ID);
}
-
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
register(DISPLAY_0);
@@ -291,6 +296,21 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetScale_noConnection_doNothing() {
+ register(TEST_DISPLAY);
+
+ // Assume that the connection does not exist.
+ mMockMagnificationConnectionState = false;
+
+ final float scale = 2.0f;
+ final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ assertFalse(mFullScreenMagnificationController
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
+ }
+
+ @Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 7fbd521..f482ddc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -197,6 +197,8 @@
private final Scroller mMockScroller = spy(new Scroller(mContext));
+ private boolean mMockMagnificationConnectionState;
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -229,6 +231,7 @@
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mockController,
@@ -238,7 +241,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- TimeAnimator::new) {
+ TimeAnimator::new,
+ () -> mMockMagnificationConnectionState) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 58567ca..2528177 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -220,7 +220,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mTimeAnimator));
+ () -> mTimeAnimator,
+ () -> true));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7c0dbf4..c6f3eb3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -34,6 +34,7 @@
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.SCHEDULED_STOP_BACKGROUND_USER_MSG;
import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
@@ -323,7 +324,8 @@
@Test
public void testStartUserUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
verify(mInjector, never()).showUserSwitchingDialog(
@@ -393,7 +395,8 @@
@Test
public void testFailedStartUserInForeground() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
@@ -470,7 +473,8 @@
@Test
public void testContinueUserSwitch() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -483,7 +487,7 @@
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(0)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@@ -491,7 +495,8 @@
public void testContinueUserSwitchDismissKeyguard() {
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -504,14 +509,15 @@
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@Test
public void testContinueUserSwitchUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
@@ -524,11 +530,11 @@
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, never()).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
}
private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
- boolean backgroundUserStopping) {
+ boolean backgroundUserStopping, boolean expectScheduleBackgroundUserStopping) {
Set<Integer> expectedCodes = new LinkedHashSet<>();
expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -536,6 +542,9 @@
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
expectedCodes.add(0); // this is for directly posting in stopping.
}
+ if (expectScheduleBackgroundUserStopping) {
+ expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
+ }
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
assertEquals("Unexpected message sent", expectedCodes, actualCodes);
Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -571,6 +580,112 @@
).collect(Collectors.toList()), Collections.emptySet());
}
+ /** Test scheduling stopping of background users after a user-switch. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_switch() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ setUpUser(TEST_USER_ID1, NO_USERINFO_FLAGS);
+
+ // Switch to TEST_USER_ID from user 0
+ int numberOfUserSwitches = 0;
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ false);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete (there should be no scheduled stopping).
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Switch to TEST_USER_ID1 from TEST_USER_ID
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1),
+ mUserController.getRunningUsersLU());
+
+ // Switch back to TEST_USER_ID from TEST_USER_ID1
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, TEST_USER_ID1,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete.
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID);
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+ }
+
+ /** Test scheduling stopping of background users that were started in the background. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ // Start two full background users (which should both get scheduled for stopping)
+ // and one profile (which should not).
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+
+ // Now that we've processed the stops, let's make sure that a subsequent one will work too.
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2, TEST_USER_ID3),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID3);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ }
+
+ /**
+ * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
+ * @param userId the user we are checking to see whether it is scheduled.
+ * Can be null, when expectScheduled is false, to indicate no user should be
+ * scheduled.
+ */
+ private void assertAndProcessScheduledStopBackgroundUser(
+ boolean expectScheduled, @Nullable Integer userId) {
+ TestHandler handler = mInjector.mHandler;
+ if (expectScheduled) {
+ assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId);
+ mUserController.processScheduledStopOfBackgroundUser(userId);
+ } else {
+ assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ }
+ }
+
@Test
public void testExplicitSystemUserStartInBackground() {
setUpUser(UserHandle.USER_SYSTEM, 0);
@@ -587,13 +702,14 @@
public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -601,7 +717,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID, USER_ID1
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
@@ -609,7 +725,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
// skip middle step and call this directly.
mUserController.finishUserSwitch(ussUser2);
@@ -631,13 +747,14 @@
public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -645,7 +762,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID1
// stopped + unlocked: USER_ID
numberOfUserSwitches++;
@@ -663,7 +780,7 @@
.lockCeStorage(anyInt());
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID2
// stopped + unlocked: USER_ID1
// stopped + locked: USER_ID
@@ -686,7 +803,8 @@
public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -707,7 +825,7 @@
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -722,7 +840,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -747,7 +865,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
// We've now done a user switch and should notice that we've exceeded the maximum number of
@@ -766,7 +884,8 @@
@Test
public void testRunningUsersListOrder_parentAfterProfile() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -787,7 +906,7 @@
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
mUserController.getRunningUsersLU());
@@ -799,7 +918,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
mUserController.getRunningUsersLU());
@@ -827,7 +946,8 @@
@Test
public void testRunningUsersListOrder_currentAtEnd() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int CURRENT_ID = 200;
final int PROFILE_ID = 201;
@@ -842,7 +962,7 @@
new Integer[] {SYSTEM_USER_ID}),
mUserController.getRunningUsersLU());
- addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+ addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
mUserController.getRunningUsersLU());
@@ -864,7 +984,8 @@
@Test
public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpAndStartUserInBackground(TEST_USER_ID);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
@@ -922,7 +1043,8 @@
@Test
public void testUserLockingForDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
setUpAndStartUserInBackground(TEST_USER_ID);
@@ -973,7 +1095,8 @@
@Test
public void testStopProfile_doesNotStopItsParent() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final Range<Integer> RUNNING_RANGE =
Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
@@ -1053,7 +1176,8 @@
@Test
public void testStopPrivateProfile() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1071,7 +1195,8 @@
@Test
public void testStopPrivateProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1083,7 +1208,8 @@
@Test
public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(
@@ -1113,7 +1239,8 @@
public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1130,7 +1257,8 @@
@Test
public void testStopManagedProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1285,7 +1413,8 @@
public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception {
// enable user switch ui, because keyguard is only shown then
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
@@ -1365,7 +1494,8 @@
}
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
- int expectedNumberOfCalls, boolean expectOldUserStopping) {
+ int expectedNumberOfCalls, boolean expectOldUserStopping,
+ boolean expectScheduleBackgroundUserStopping) {
// Start user -- this will update state of mUserController
mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1378,8 +1508,12 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
+ assertEquals(mInjector.mHandler
+ .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId),
+ expectScheduleBackgroundUserStopping);
verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
+ continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping,
+ expectScheduleBackgroundUserStopping);
}
private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5033a380..9a58594 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,10 +46,13 @@
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
@@ -110,6 +113,9 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -140,6 +146,9 @@
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -148,6 +157,7 @@
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -160,6 +170,8 @@
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.time.Clock;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -171,7 +183,7 @@
import java.util.concurrent.ThreadLocalRandom;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class PreferencesHelperTest extends UiServiceTestCase {
private static final int UID_HEADLESS = 1000000;
private static final UserHandle USER = UserHandle.of(0);
@@ -212,6 +224,22 @@
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ Clock mClock;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_PERSIST_INCOMPLETE_RESTORE_DATA);
+ }
+
+ public PreferencesHelperTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -326,13 +354,14 @@
currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
}
when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -680,7 +709,7 @@
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -816,7 +845,7 @@
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -875,7 +904,7 @@
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ false);
+ /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -934,7 +963,7 @@
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1010,7 +1039,8 @@
when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234);
final ApplicationInfo app = new ApplicationInfo();
app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app);
+ when(mPm.getApplicationInfoAsUser(
+ eq("something"), anyInt(), eq(USER_SYSTEM))).thenReturn(app);
mXmlHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234});
@@ -1452,6 +1482,149 @@
assertTrue(actualChannel.isSoundRestored());
}
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore() throws Exception {
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // settings are not available with real uid because pkg is not installed
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false)).isNull();
+ // but the settings are in memory with unknown_uid
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNotNull();
+
+ // package is "installed"
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+
+ // Trigger 2nd restore pass
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ false);
+ assertThat(channel.getImportance()).isEqualTo(2);
+ assertThat(channel.canShowBadge()).isTrue();
+ assertThat(channel.canBypassDnd()).isFalse();
+
+ // removed from 'pending install' set
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id,false)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore_afterReboot() throws Exception {
+ // load restore data
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // simulate write to disk
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+ serializer.endDocument();
+ serializer.flush();
+
+ // simulate load after reboot
+ mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+ false, mClock);
+ loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+
+ // Trigger 2nd restore pass
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ false);
+ assertThat(channel.getImportance()).isEqualTo(2);
+ assertThat(channel.canShowBadge()).isTrue();
+ assertThat(channel.canBypassDnd()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore_packageMissingAfterTwoDays() throws Exception {
+ // load restore data
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // simulate write to disk
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+ serializer.endDocument();
+ serializer.flush();
+
+ // advance time by 2 days
+ when(mClock.millis()).thenReturn(
+ Duration.ofDays(2).toMillis() + System.currentTimeMillis());
+
+ // simulate load after reboot
+ mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+ false, mClock);
+ loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+
+ // Trigger 2nd restore pass
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ // verify the 2nd restore pass failed because the restore data had been removed
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNull();
+ }
/**
* Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
@@ -1520,10 +1693,10 @@
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3f5217c..8ca8623 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -39,6 +39,7 @@
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -59,6 +60,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -177,6 +180,24 @@
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testOnRequestVibrationParamsComplete_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ vibrationParamList.toArray(new VibrationParam[0]));
+ }
+
@Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
@@ -214,6 +235,19 @@
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetVibrationParams_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.setVibrationParams(
+ vibrationParamList.toArray(new VibrationParam[0]),
+ mFakeVibratorController);
+ }
+
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
index a606388..c17d11e 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -42,7 +42,10 @@
return vibrationParamList.toArray(new VibrationParam[0]);
}
- private static VibrationParam generateVibrationParam(int type, float scale) {
+ /**
+ * Generates a {@link VibrationParam} with the specified type and scale.
+ */
+ public static VibrationParam generateVibrationParam(int type, float scale) {
ScaleParam scaleParam = new ScaleParam();
scaleParam.typesMask = type;
scaleParam.scale = scale;
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index c3da903..7322e5a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -54,6 +54,8 @@
import android.os.PowerManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.view.Display;
+import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
@@ -82,6 +84,8 @@
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock PowerManager mPowerManager;
+ @Mock WindowManager mWindowManager;
+ @Mock Display mDefaultDisplay;
@Mock Clock mClock;
@Mock WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
@@ -96,7 +100,10 @@
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+ when(mContextSpy.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+ when(mWindowManager.getDefaultDisplay()).thenReturn(mDefaultDisplay);
LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
+ setDefaultDisplayState(Display.STATE_OFF);
}
@Test
@@ -199,6 +206,19 @@
}
@Test
+ public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ setDefaultDisplayState(Display.STATE_ON);
+ setTheaterModeEnabled(true);
+ setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+
+ verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
+ }
+
+ @Test
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
@@ -291,6 +311,7 @@
Mockito.reset(mPowerManager);
setBooleanRes(theatherModeWakeResId, true);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
setUptimeMillis(200);
assertWithMessage("Wake should happen in theater mode when config allows it.")
@@ -299,6 +320,7 @@
Mockito.reset(mPowerManager);
setBooleanRes(theatherModeWakeResId, false);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
setUptimeMillis(250);
assertWithMessage("Wake should not happen in theater mode when config disallows it.")
@@ -310,6 +332,7 @@
Mockito.reset(mPowerManager);
setBooleanRes(theatherModeWakeResId, true);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
setUptimeMillis(300);
assertWithMessage("Wake should happen when not in theater mode.")
@@ -318,6 +341,7 @@
Mockito.reset(mPowerManager);
setBooleanRes(theatherModeWakeResId, false);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
setUptimeMillis(350);
assertWithMessage("Wake should happen when not in theater mode.")
@@ -351,4 +375,8 @@
when(mInputWakeUpDelegate.wakeUpFromKey(anyLong(), anyInt(), anyBoolean()))
.thenReturn(result);
}
+
+ private void setDefaultDisplayState(int displayState) {
+ when(mDefaultDisplay.getState()).thenReturn(displayState);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5aabea3..b41db31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -643,7 +643,8 @@
doReturn(false).when(mActivity).isInLetterboxAnimation();
assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
- doReturn(false).when(mainWindow).isOnScreen();
+ doReturn(false).when(mActivity).isVisibleRequested();
+ doReturn(false).when(mActivity).isVisible();
assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
doReturn(true).when(mActivity).isInLetterboxAnimation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2e80bc7..9697c65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -130,7 +130,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -916,8 +915,7 @@
assertEquals(window, mActivity.findMainWindow());
spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceVisible(any());
+ doReturn(true).when(mActivity).isVisibleRequested();
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
mActivity.findMainWindow()));
@@ -1943,8 +1941,7 @@
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
-
- final int scale = dh / dw;
+ final int scale = dh / dw;
// App bounds should be dh / scale x dw / scale
assertEquals(dw, rotatedDisplayBounds.width());
@@ -4179,13 +4176,8 @@
}
@Test
- @Ignore // TODO(b/330888878): fix test in main
- public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
- if (Flags.insetsDecoupledConfiguration()) {
- // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
- // bounds no longer contains display cutout.
- return;
- }
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
// Set up portrait close to square display
setUpDisplaySizeWithApp(2200, 2280);
final DisplayContent display = mActivity.mDisplayContent;
@@ -4198,16 +4190,58 @@
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(navbar.providesDisplayDecorInsets()
- && display.getDisplayPolicy().updateDecorInsetsInfo());
+ assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
display.sendNewConfiguration();
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
- // Activity is fullscreen even though orientation is not respected with insets, because
- // the display still matches or is less than the activity aspect ratio
- assertEquals(display.getBounds(), mActivity.getBounds());
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ final Rect bounds = activity.getBounds();
+ // Activity should be letterboxed and should have portrait app bounds
+ assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(bounds.height() > bounds.width());
+ }
+
+ @Test
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+ setUpDisplaySizeWithApp(2200, 2280);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ final DisplayContent dc = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+ dc.sendNewConfiguration();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+ // To force config to update again but with the same landscape orientation.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+ assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertNotNull(activity.getCompatDisplayInsets());
+ // Activity is not letterboxed for fixed orientation because orientation is respected
+ // with insets, and should not be in size compat mode
+ assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.inSizeCompatMode());
}
@Test
@@ -4229,6 +4263,7 @@
// can be aligned inside parentAppBounds
assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
}
+
@Test
public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
if (Flags.insetsDecoupledConfiguration()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1ca808f..225e85e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -833,8 +833,11 @@
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
final ActivityRecord.CompatDisplayInsets compatInsets =
new ActivityRecord.CompatDisplayInsets(
- display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
+ display, activity, /* letterboxedContainerBounds */ null,
+ /* useOverrideInsets */ false);
+ final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
+ overrideHint.mTmpCompatInsets = compatInsets;
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, overrideHint);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba532..8fe45cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@
}
/**
+ * Set owner for this subscription.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setGroupOwner(subscriptionId, groupOwner);
+ } else {
+ throw new IllegalStateException("[setGroupOwner]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Set userHandle for a subscription.
*
* Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f40..1bfec29 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@
*/
int setUsageSetting(int usageSetting, int subId, String callingPackage);
+ /**
+ * Set owner for this subscription.
+ *
+ * @param subId the unique SubscriptionInfo index in database
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ void setGroupOwner(int subId, String groupOwner);
+
/**
* Set userHandle for this subscription.
*
diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp
index 5d49120..3f48d70 100644
--- a/tests/ChoreographerTests/Android.bp
+++ b/tests/ChoreographerTests/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/CtsSurfaceControlTestsStaging/Android.bp b/tests/CtsSurfaceControlTestsStaging/Android.bp
index 96e4a9e..1038c9e 100644
--- a/tests/CtsSurfaceControlTestsStaging/Android.bp
+++ b/tests/CtsSurfaceControlTestsStaging/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1fdf97a..093923f 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Xml;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp
index 4ef1ead..7990732 100644
--- a/tests/TouchLatency/Android.bp
+++ b/tests/TouchLatency/Android.bp
@@ -5,6 +5,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index b23a87e..fa352cf 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,6 +18,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
</resources>
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
index 61dd9d4..dbe9d36 100644
--- a/tests/WindowInsetsTests/AndroidManifest.xml
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -18,7 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.test.windowinsetstests">
- <application android:label="@string/application_title">
+ <application android:label="@string/application_title"
+ android:theme="@style/base">
<activity android:name=".WindowInsetsTestsMainActivity"
android:exported="true">
<intent-filter>
@@ -29,11 +30,9 @@
<activity android:name=".ChatActivity"
android:label="@string/chat_activity_title"
- android:theme="@style/chat"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".ControllerActivity"
- android:label="@string/controller_activity_title"
- android:theme="@style/controller" />
+ android:label="@string/controller_activity_title" />
</application>
</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml
index 5550eab..7013059 100644
--- a/tests/WindowInsetsTests/res/layout/controller_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml
@@ -15,92 +15,110 @@
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <LinearLayout
- android:id="@+id/content"
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
+
+ <ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
- <Spinner
- android:id="@+id/spinnerBehavior"
+ <LinearLayout
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp" />
+ android:orientation="vertical">
- <ToggleButton
- android:id="@+id/toggleButtonStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Status Bars Toggle Button"
- android:textOff="Status Bars Invisible"
- android:textOn="Status Bars Visible" />
+ <Spinner
+ android:id="@+id/spinnerBehavior"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp" />
- <SeekBar
- android:id="@+id/seekBarStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
+ <ToggleButton
+ android:id="@+id/toggleButtonStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/status_bars_toggle_button"
+ android:textOff="@string/status_bars_invisible"
+ android:textOn="@string/status_bars_visible" />
- <ToggleButton
- android:id="@+id/toggleButtonNavigation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Navigation Bars Toggle Button"
- android:textOff="Navigation Bars Invisible"
- android:textOn="Navigation Bars Visible" />
+ <SeekBar
+ android:id="@+id/seekBarStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
- <SeekBar
- android:id="@+id/seekBarNavigation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
+ <ToggleButton
+ android:id="@+id/toggleButtonNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/navigation_bars_toggle_button"
+ android:textOff="@string/navigation_bars_invisible"
+ android:textOn="@string/navigation_bars_visible" />
- <ToggleButton
- android:id="@+id/toggleButtonIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="IME Toggle Button"
- android:textOff="IME Invisible"
- android:textOn="IME Visible" />
+ <SeekBar
+ android:id="@+id/seekBarNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
- <SeekBar
- android:id="@+id/seekBarIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="0" />
+ <ToggleButton
+ android:id="@+id/toggleButtonIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/ime_toggle_button"
+ android:textOff="@string/ime_invisible"
+ android:textOn="@string/ime_visible" />
- <TextView
- android:id="@+id/textViewControllableInsets"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="5dp" />
+ <SeekBar
+ android:id="@+id/seekBarIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="0" />
- <EditText
- android:id="@+id/editText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ems="10"
- android:hint="For testing IME..."
- android:inputType="text"
- android:text="" />
+ <TextView
+ android:id="@+id/textViewControllableInsets"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp" />
- </LinearLayout>
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:autofillHints="@string/for_testing_ime"
+ android:hint="@string/for_testing_ime"
+ android:inputType="text"
+ android:text="" />
-</ScrollView>
\ No newline at end of file
+ </LinearLayout>
+
+ </ScrollView>
+
+</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/layout/main_activity.xml b/tests/WindowInsetsTests/res/layout/main_activity.xml
index 621ed89..d6d4ff9 100644
--- a/tests/WindowInsetsTests/res/layout/main_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/main_activity.xml
@@ -16,22 +16,38 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <Button
- android:id="@+id/chat_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/chat_activity_title"
- android:textAllCaps="false"/>
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
- <Button
- android:id="@+id/controller_button"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/controller_activity_title"
- android:textAllCaps="false"/>
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
+
+ <Button
+ android:id="@+id/chat_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chat_activity_title"
+ android:textAllCaps="false"/>
+
+ <Button
+ android:id="@+id/controller_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/controller_activity_title"
+ android:textAllCaps="false"/>
+
+ </LinearLayout>
</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/values-night/styles.xml b/tests/WindowInsetsTests/res/values-night/styles.xml
new file mode 100644
index 0000000..323c5fd
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values-night/styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="base" parent="@style/Theme.MaterialComponents">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+
+ <item name="colorPrimary">@color/primaryColor</item>
+ <item name="colorPrimaryDark">@color/primaryDarkColor</item>
+ <item name="colorSecondary">?attr/colorPrimary</item>
+ <item name="colorOnSecondary">@color/primaryTextColor</item>
+
+ <!-- Window decor -->
+ <item name="android:windowLightStatusBar">false</item>
+ <item name="android:windowLightNavigationBar">false</item>
+
+ </style>
+
+ <color name="primaryColor">#639ff9</color>
+ <color name="primaryLightColor">#6f6bff</color>
+ <color name="primaryDarkColor">#0016bb</color>
+ <color name="primaryTextColor">#ffffff</color>
+
+ <color name="bubble">#333333</color>
+ <color name="bubble_self">#185abc</color>
+
+</resources>
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
index 516d458..7b70852 100644
--- a/tests/WindowInsetsTests/res/values/strings.xml
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -19,6 +19,16 @@
<string name="application_title">Window Insets Tests</string>
<string name="chat_activity_title">New Insets Chat</string>
<string name="controller_activity_title">Window Insets Controller</string>
+ <string name="status_bars_toggle_button">Status Bars Toggle Button</string>
+ <string name="status_bars_invisible">Status Bars Invisible</string>
+ <string name="status_bars_visible">Status Bars Visible</string>
+ <string name="navigation_bars_toggle_button">Navigation Bars Toggle Button</string>
+ <string name="navigation_bars_invisible">Navigation Bars Invisible</string>
+ <string name="navigation_bars_visible">Navigation Bars Visible</string>
+ <string name="ime_toggle_button">IME Bars Toggle Button</string>
+ <string name="ime_invisible">IME Bars Invisible</string>
+ <string name="ime_visible">IME Bars Visible</string>
+ <string name="for_testing_ime">For testing IME…</string>
<!-- The item positions should match the flag values respectively. -->
<string-array name="behaviors">
diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml
index a84ffbed..4ce6323 100644
--- a/tests/WindowInsetsTests/res/values/styles.xml
+++ b/tests/WindowInsetsTests/res/values/styles.xml
@@ -17,7 +17,7 @@
<resources>
- <style name="chat" parent="@style/Theme.MaterialComponents.Light">
+ <style name="base" parent="@style/Theme.MaterialComponents.Light">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@@ -27,10 +27,8 @@
<item name="colorOnSecondary">@color/primaryTextColor</item>
<!-- Window decor -->
- <item name="android:statusBarColor">#ffffff</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
- <item name="android:navigationBarColor">#ffffff</item>
</style>
@@ -63,11 +61,4 @@
<dimen name="bubble_padding">8dp</dimen>
<dimen name="bubble_padding_side">16dp</dimen>
- <style name="controller" parent="android:Theme.Material">
- <item name="android:colorPrimaryDark">#111111</item>
- <item name="android:navigationBarColor">#111111</item>
- <item name="android:colorPrimary">#222222</item>
- <item name="android:colorAccent">#33ccff</item>
- </style>
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index 167d560..1dd87df 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -16,12 +16,18 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
+
import android.graphics.Insets;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.widget.AdapterView;
@@ -32,7 +38,9 @@
import android.widget.TextView;
import android.widget.ToggleButton;
-public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener {
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ControllerActivity extends AppCompatActivity {
private ToggleButton mToggleStatus;
private SeekBar mSeekStatus;
@@ -48,6 +56,29 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.controller_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getWindow().setDecorFitsSystemWindows(false);
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final int visibleTypes = systemBars() | displayCutout();
+ final Insets i = insets.getInsets(visibleTypes);
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+
+ // Make the content view not obscured by gesture insets to prevent triggering
+ // system gestures while controlling seek bars.
+ final Insets gi = Insets.subtract(
+ insets.getInsets(systemGestures() | visibleTypes), i);
+ findViewById(R.id.content).setPadding(gi.left, gi.top, gi.right, gi.bottom);
+
+ mNotFromUser[0] = true;
+ updateWidgets(insets, statusBars(), mToggleStatus, mSeekStatus);
+ updateWidgets(insets, navigationBars(), mToggleNavigation, mSeekNavigation);
+ updateWidgets(insets, ime(), mToggleIme, mSeekIme);
+ mLastInsets = insets;
+ mNotFromUser[0] = false;
+
+ return WindowInsets.CONSUMED;
+ });
final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior);
ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this,
R.array.behaviors, android.R.layout.simple_spinner_item);
@@ -66,23 +97,21 @@
});
mToggleStatus = findViewById(R.id.toggleButtonStatus);
mToggleStatus.setTag(mNotFromUser);
- mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars()));
+ mToggleStatus.setOnCheckedChangeListener(new ToggleListener(statusBars()));
mSeekStatus = findViewById(R.id.seekBarStatus);
- mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars()));
+ mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(statusBars()));
mToggleNavigation = findViewById(R.id.toggleButtonNavigation);
mToggleNavigation.setTag(mNotFromUser);
- mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars()));
+ mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(navigationBars()));
mSeekNavigation = findViewById(R.id.seekBarNavigation);
- mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars()));
+ mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(navigationBars()));
mToggleIme = findViewById(R.id.toggleButtonIme);
mToggleIme.setTag(mNotFromUser);
- mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime()));
+ mToggleIme.setOnCheckedChangeListener(new ToggleListener(ime()));
mSeekIme = findViewById(R.id.seekBarIme);
- mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime()));
+ mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(ime()));
mTextControllableInsets = findViewById(R.id.textViewControllableInsets);
- final View contentView = findViewById(R.id.content);
- contentView.setOnApplyWindowInsetsListener(this);
- contentView.getWindowInsetsController().addOnControllableInsetsChangedListener(
+ mTextControllableInsets.getWindowInsetsController().addOnControllableInsetsChangedListener(
(c, types) -> mTextControllableInsets.setText(
"ControllableInsetsTypes:\n" + insetsTypesToString(types)));
}
@@ -91,22 +120,6 @@
return types == 0 ? "none" : WindowInsets.Type.toString(types);
}
- @Override
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- mNotFromUser[0] = true;
- updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus);
- updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation);
- updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme);
- mLastInsets = insets;
- mNotFromUser[0] = false;
-
- // Prevent triggering system gestures while controlling seek bars.
- final Insets gestureInsets = insets.getInsets(Type.systemGestures());
- v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0);
-
- return v.onApplyWindowInsets(insets);
- }
-
private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) {
final boolean isVisible = insets.isVisible(types);
final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible;
@@ -121,7 +134,7 @@
private static class ToggleListener implements CompoundButton.OnCheckedChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
ToggleListener(int types) {
mTypes = types;
@@ -143,7 +156,7 @@
private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
private WindowInsetsAnimationController mController;
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
index 8b77a78..278ad84 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
@@ -16,16 +16,30 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
-public class WindowInsetsTestsMainActivity extends Activity {
+import android.content.Intent;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.view.WindowInsets;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class WindowInsetsTestsMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final Insets i = insets.getInsets(systemBars() | displayCutout());
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+ return WindowInsets.CONSUMED;
+ });
findViewById(R.id.chat_button).setOnClickListener(
v -> startActivity(new Intent(this, ChatActivity.class)));
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6189fb0..edad678 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -107,7 +107,6 @@
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected ConnectivityManager mConnectivityManager;
@Mock protected TelephonyManager mTelephonyManager;
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
index 9d61111..be5c197 100644
--- a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -18,11 +18,13 @@
#include <stdio.h>
+#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
+#include <unordered_set>
#include "Errors.h"
@@ -30,21 +32,39 @@
using namespace google::protobuf::io;
using namespace std;
+static bool outer_class_name_clashes_with_any_message(const string& outer_class_name,
+ const vector<DescriptorProto>& messages) {
+ return any_of(messages.cbegin(), messages.cend(), [&](const DescriptorProto& message) {
+ return message.name() == outer_class_name;
+ });
+}
+
/**
* If the descriptor gives us a class name, use that. Otherwise make one up from
* the filename of the .proto file.
*/
-static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor,
+ const vector<DescriptorProto>& messages) {
string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
+ if (!name.empty()) {
+ return name;
}
+
+ // Outer class and messages with the same name would result in invalid java (outer class and
+ // inner class cannot have same names).
+ // If the outer class name clashes with any message, let's append an "OuterClass" suffix.
+ // This behavior is consistent with the standard protoc.
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ while (outer_class_name_clashes_with_any_message(name, messages)) {
+ name += "OuterClass";
+ }
+
+ if (name.empty()) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+
return name;
}
@@ -149,6 +169,12 @@
write_field(text, message.field(i), indented);
}
+ // Extensions
+ N = message.extension_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.extension(i), indented);
+ }
+
text << indent << "}" << endl;
text << endl;
}
@@ -165,7 +191,7 @@
stringstream text;
string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor, messages);
text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
text << "// source: " << file_descriptor.name() << endl << endl;
@@ -214,7 +240,7 @@
*/
static void write_multiple_files(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
// If there is anything to put in the outer class file, create one
if (file_descriptor.enum_type_size() > 0) {
vector<EnumDescriptorProto> enums;
@@ -222,7 +248,7 @@
for (int i = 0; i < N; i++) {
auto enum_full_name =
file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
enums.push_back(file_descriptor.enum_type(i));
@@ -230,9 +256,10 @@
vector<DescriptorProto> messages;
- if (messages_to_compile.empty() || !enums.empty()) {
+ if (messages_allowlist.empty() || !enums.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
true, enums, messages);
}
}
@@ -246,12 +273,12 @@
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
- if (messages_to_compile.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !messages.empty()) {
write_file(response, file_descriptor,
make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
false, enums, messages);
@@ -261,14 +288,14 @@
static void write_single_file(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
int N;
vector<EnumDescriptorProto> enums;
N = file_descriptor.enum_type_size();
for (int i = 0; i < N; i++) {
auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
@@ -281,22 +308,23 @@
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
}
- if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !enums.empty() || !messages.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
- enums, messages);
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
+ true, enums, messages);
}
}
static void parse_args_string(stringstream args_string_stream,
- set<string>* messages_to_compile_out) {
+ unordered_set<string>& messages_allowlist_out) {
string line;
while (getline(args_string_stream, line, ';')) {
stringstream line_ss(line);
@@ -305,7 +333,7 @@
if (arg_name == "include_filter") {
string full_message_name;
while (getline(line_ss, full_message_name, ',')) {
- messages_to_compile_out->insert(full_message_name);
+ messages_allowlist_out.insert(full_message_name);
}
} else {
ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
@@ -316,10 +344,10 @@
CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
CodeGeneratorResponse response;
- set<string> messages_to_compile;
+ unordered_set<string> messages_allowlist;
auto request_params = request.parameter();
if (!request_params.empty()) {
- parse_args_string(stringstream(request_params), &messages_to_compile);
+ parse_args_string(stringstream(request_params), messages_allowlist);
}
// Build the files we need.
@@ -328,9 +356,9 @@
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor, messages_to_compile);
+ write_multiple_files(&response, file_descriptor, messages_allowlist);
} else {
- write_single_file(&response, file_descriptor, messages_to_compile);
+ write_single_file(&response, file_descriptor, messages_allowlist);
}
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 45ab986..2ba5705 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -993,6 +993,16 @@
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
+ * <p>
+ * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the
+ * returned scan results should include separate scan result for each BSSID within the
+ * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs.
+ * Original Multi-BSSID Element will be included in the Information Elements attached to
+ * each of the scan results.
+ * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an
+ * optional requirement for devices running with older WiFi generations.
+ * </p>
+ *
* @param ifaceName Name of the interface.
* @param scanType The type of scan result to be returned, can be
* {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.