Merge "Add a placeholder to show the hint of text on dialog" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 24cd610..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",
@@ -1333,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/core/api/current.txt b/core/api/current.txt
index e2d860e..c7fce1b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32742,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/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/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/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/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/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/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/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/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/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/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/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/hwui/Android.bp b/libs/hwui/Android.bp
index eceba92..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"],
}
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 212e390..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.
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 a0ebbfe..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.");
@@ -218,7 +209,7 @@
@MainThread
private void stopDiscoveryAndFinish(boolean timeout) {
- if (DEBUG) Log.i(TAG, "stopDiscovery()");
+ Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")");
if (!mDiscoveryStarted) {
stopSelf();
@@ -298,10 +289,9 @@
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);
@@ -313,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);
@@ -329,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;
}
@@ -350,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);
@@ -376,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);
@@ -395,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);
}
@@ -419,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;
@@ -447,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);
@@ -477,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);
@@ -505,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/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/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/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/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/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/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 94515d3..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
@@ -1141,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/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/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
new file mode 100644
index 0000000..e0bb26e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.keyguard.ui.composable.blueprint
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface DefaultBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
+}
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/res/color/notification_focus_overlay_color.xml b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
new file mode 100644
index 0000000..6a3c7a1
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_focus_overlay_color.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"
+ 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/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/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/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/log/dagger/DeviceEntryIconLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
new file mode 100644
index 0000000..f3414b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
@@ -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.
+ */
+package com.android.systemui.log.dagger
+
+import java.lang.annotation.Documented
+import javax.inject.Qualifier
+
+/** 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/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/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/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/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/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/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/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/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/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/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/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 5846c92..eb2ef29 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -154,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 cadbd5e..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,9 +4621,6 @@
"connection=" + connection);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE);
-
getMagnificationConnectionManager().setConnection(connection);
if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
@@ -4660,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",
@@ -4672,6 +4672,7 @@
}
@Override
+ @RequiresNoPermission
public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy",
@@ -4688,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);
@@ -4709,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);
@@ -4730,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",
@@ -4752,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)) {
@@ -4763,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;
@@ -4826,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 {
@@ -4842,6 +4844,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationSequence(String opPkg,
@FlashNotificationReason int reason, IBinder token) {
final long identity = Binder.clearCallingIdentity();
@@ -4854,6 +4857,7 @@
}
@Override
+ @RequiresNoPermission
public boolean stopFlashNotificationSequence(String opPkg) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4864,6 +4868,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationEvent(String opPkg,
@FlashNotificationReason int reason, String reasonPkg) {
final long identity = Binder.clearCallingIdentity();
@@ -4876,6 +4881,7 @@
}
@Override
+ @RequiresNoPermission
public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4917,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)) {
@@ -4950,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.
@@ -4997,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) {
@@ -5139,6 +5148,7 @@
}
@Override
+ @RequiresNoPermission
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
@@ -6159,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;
@@ -6246,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,
@@ -6261,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/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/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/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 1f09d4d..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);
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/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e47d416..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();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4568624..6612319 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15502,6 +15502,7 @@
intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
} else {
mAppOpsService.uidRemoved(uid);
+ mServices.onUidRemovedLocked(uid);
}
}
break;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5b15c37..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)) {
@@ -4196,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:");
@@ -4211,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");
@@ -4302,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:");
@@ -4311,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/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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1bd93e4..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);
}
}
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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index aa18175..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;
@@ -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/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/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/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/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/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 8042010..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() {
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/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/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/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 6cc650f..cb4fc75 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -69,8 +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;
@@ -200,25 +202,26 @@
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(
@@ -253,7 +256,8 @@
mMockA11yDisplayListener,
mMockMagnificationController,
mInputFilter,
- mProxyManager);
+ mProxyManager,
+ mFakePermissionEnforcer);
final AccessibilityUserState userState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
@@ -310,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);
@@ -321,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);
@@ -338,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);
}
@@ -358,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(),
@@ -369,6 +372,7 @@
@SmallTest
@Test
public void testRegisterProxyWithoutA11yPermissionOrRole() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
doThrow(SecurityException.class).when(mMockSecurityPolicy)
.checkForAccessibilityPermissionOrRole();
@@ -381,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(),
@@ -402,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(),
@@ -411,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);
@@ -432,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);
@@ -577,6 +579,7 @@
@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(
@@ -789,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());
@@ -807,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());
@@ -921,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();
@@ -929,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"));
@@ -943,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(
@@ -964,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);
@@ -1007,7 +1010,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1035,7 +1038,7 @@
AccessibilityShortcutController.DialogStatus.NOT_SHOWN
);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1079,7 +1082,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1098,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,
@@ -1125,7 +1128,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1167,7 +1170,7 @@
@Test
public void enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1213,7 +1216,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1256,7 +1259,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1300,7 +1303,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1332,10 +1335,11 @@
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
@@ -1343,7 +1347,7 @@
// 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));
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1403,7 +1407,7 @@
@Test
public void getA11yFeatureToTileMap() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
Bundle bundle = mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked());
@@ -1428,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(
@@ -1442,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);
@@ -1456,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
@@ -1493,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(),
@@ -1511,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();
@@ -1533,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
@@ -1661,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(),
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/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/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/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/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);
}
}
}