Merge "Update shortcut helper string for "open notes"." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 9ee74e3..1c6df75 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -56,6 +56,7 @@
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":android.systemserver.flags-aconfig-java{.generated_srcjars}",
":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
@@ -1159,3 +1160,16 @@
host_supported: true,
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// System Server
+aconfig_declarations {
+ name: "android.systemserver.flags-aconfig",
+ package: "android.server",
+ srcs: ["services/java/com/android/server/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.systemserver.flags-aconfig-java",
+ aconfig_declarations: "android.systemserver.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 324d8ca..7284f47 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -23,7 +23,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.util.TimeUtils.formatDuration;
import android.annotation.BytesLong;
@@ -50,9 +49,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.Trace;
-import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
@@ -206,6 +203,8 @@
/* Minimum flex for a periodic job, in milliseconds. */
private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
+ private static final long MIN_ALLOWED_TIME_WINDOW_MILLIS = MIN_PERIOD_MILLIS;
+
/**
* Minimum backoff interval for a job, in milliseconds
* @hide
@@ -1881,11 +1880,12 @@
}
/**
- * Set deadline which is the maximum scheduling latency. The job will be run by this
- * deadline even if other requirements (including a delay set through
- * {@link #setMinimumLatency(long)}) are not met.
+ * Set a deadline after which all other functional requested constraints will be ignored.
+ * After the deadline has passed, the job can run even if other requirements (including
+ * a delay set through {@link #setMinimumLatency(long)}) are not met.
* {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's
- * deadline has passed.
+ * deadline has passed. The job's execution may be delayed beyond the set deadline by
+ * other factors such as Doze mode and system health signals.
*
* <p>
* Because it doesn't make sense setting this property on a periodic job, doing so will
@@ -1894,30 +1894,23 @@
*
* <p class="note">
* Since a job will run once the deadline has passed regardless of the status of other
- * constraints, setting a deadline of 0 with other constraints makes those constraints
- * meaningless when it comes to execution decisions. Avoid doing this.
- * </p>
- *
- * <p>
- * Short deadlines hinder the system's ability to optimize scheduling behavior and may
- * result in running jobs at inopportune times. Therefore, starting in Android version
- * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, minimum time windows will be
- * enforced to help make it easier to better optimize job execution. Time windows are
+ * constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal
+ * to the deadline) with other constraints makes those constraints
+ * meaningless when it comes to execution decisions. Since doing so is indicative of an
+ * error in the logic, starting in Android version
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, jobs with extremely short
+ * time windows will fail to build. Time windows are
* defined as the time between a job's {@link #setMinimumLatency(long) minimum latency}
* and its deadline. If the minimum latency is not set, it is assumed to be 0.
- * The following minimums will be enforced:
- * <ul>
- * <li>
- * Jobs with {@link #PRIORITY_DEFAULT} or higher priorities have a minimum time
- * window of one hour.
- * </li>
- * <li>Jobs with {@link #PRIORITY_LOW} have a minimum time window of 6 hours.</li>
- * <li>Jobs with {@link #PRIORITY_MIN} have a minimum time window of 12 hours.</li>
- * </ul>
*
* Work that must happen immediately should use {@link #setExpedited(boolean)} or
* {@link #setUserInitiated(boolean)} in the appropriate manner.
*
+ * <p>
+ * This API aimed to guarantee execution of the job by the deadline only on Android version
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. That aim and guarantee has not existed
+ * since {@link android.os.Build.VERSION_CODES#M}.
+ *
* @see JobInfo#getMaxExecutionDelayMillis()
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
@@ -2347,35 +2340,36 @@
throw new IllegalArgumentException("Invalid priority level provided: " + mPriority);
}
- if (enforceMinimumTimeWindows
- && Flags.enforceMinimumTimeWindows()
- // TODO(312197030): remove exemption for the system
- && !UserHandle.isCore(Process.myUid())
- && hasLateConstraint && !isPeriodic) {
- final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0;
- if (mPriority >= PRIORITY_DEFAULT) {
- if (maxExecutionDelayMillis - windowStart < HOUR_IN_MILLIS) {
- throw new IllegalArgumentException(
- getPriorityString(mPriority)
- + " cannot have a time window less than 1 hour."
- + " Delay=" + windowStart
- + ", deadline=" + maxExecutionDelayMillis);
- }
- } else if (mPriority >= PRIORITY_LOW) {
- if (maxExecutionDelayMillis - windowStart < 6 * HOUR_IN_MILLIS) {
- throw new IllegalArgumentException(
- getPriorityString(mPriority)
- + " cannot have a time window less than 6 hours."
- + " Delay=" + windowStart
- + ", deadline=" + maxExecutionDelayMillis);
- }
+ final boolean hasFunctionalConstraint = networkRequest != null
+ || constraintFlags != 0
+ || (triggerContentUris != null && triggerContentUris.length > 0);
+ if (hasLateConstraint && !isPeriodic) {
+ if (!hasFunctionalConstraint) {
+ Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ + " has a deadline with no functional constraints."
+ + " The deadline won't improve job execution latency."
+ + " Consider removing the deadline.");
} else {
- if (maxExecutionDelayMillis - windowStart < 12 * HOUR_IN_MILLIS) {
- throw new IllegalArgumentException(
- getPriorityString(mPriority)
- + " cannot have a time window less than 12 hours."
- + " Delay=" + windowStart
- + ", deadline=" + maxExecutionDelayMillis);
+ final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0;
+ if (maxExecutionDelayMillis - windowStart < MIN_ALLOWED_TIME_WINDOW_MILLIS) {
+ if (enforceMinimumTimeWindows
+ && Flags.enforceMinimumTimeWindows()) {
+ throw new IllegalArgumentException("Jobs with a deadline and"
+ + " functional constraints cannot have a time window less than "
+ + MIN_ALLOWED_TIME_WINDOW_MILLIS + " ms."
+ + " Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ + " has delay=" + windowStart
+ + ", deadline=" + maxExecutionDelayMillis);
+ } else {
+ Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+ + " has a deadline with functional constraints and an extremely"
+ + " short time window of "
+ + (maxExecutionDelayMillis - windowStart) + " ms"
+ + " (delay=" + windowStart
+ + ", deadline=" + maxExecutionDelayMillis + ")."
+ + " The functional constraints are not likely to be satisfied when"
+ + " the job runs.");
+ }
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cea16d6..e6ee975 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -318,7 +318,8 @@
private final List<JobRestriction> mJobRestrictions;
@GuardedBy("mLock")
- private final BatteryStateTracker mBatteryStateTracker;
+ @VisibleForTesting
+ final BatteryStateTracker mBatteryStateTracker;
@GuardedBy("mLock")
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
@@ -4261,20 +4262,34 @@
.sendToTarget();
}
- private final class BatteryStateTracker extends BroadcastReceiver {
- /**
- * Track whether we're "charging", where charging means that we're ready to commit to
- * doing work.
- */
- private boolean mCharging;
+ @VisibleForTesting
+ final class BatteryStateTracker extends BroadcastReceiver
+ implements BatteryManagerInternal.ChargingPolicyChangeListener {
+ private final BatteryManagerInternal mBatteryManagerInternal;
+
+ /** Last reported battery level. */
+ private int mBatteryLevel;
/** Keep track of whether the battery is charged enough that we want to do work. */
private boolean mBatteryNotLow;
+ /**
+ * Charging status based on {@link BatteryManager#ACTION_CHARGING} and
+ * {@link BatteryManager#ACTION_DISCHARGING}.
+ */
+ private boolean mCharging;
+ /**
+ * The most recently acquired value of
+ * {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ */
+ private int mChargingPolicy;
+ /** Track whether there is power connected. It doesn't mean the device is charging. */
+ private boolean mPowerConnected;
/** Sequence number of last broadcast. */
private int mLastBatterySeq = -1;
private BroadcastReceiver mMonitor;
BatteryStateTracker() {
+ mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
}
public void startTracking() {
@@ -4286,13 +4301,18 @@
// Charging/not charging.
filter.addAction(BatteryManager.ACTION_CHARGING);
filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
getTestableContext().registerReceiver(this, filter);
+ mBatteryManagerInternal.registerChargingPolicyChangeListener(this);
+
// Initialise tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
- mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ mBatteryNotLow = !mBatteryManagerInternal.getBatteryLevelLow();
+ mCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mChargingPolicy = mBatteryManagerInternal.getChargingPolicy();
}
public void setMonitorBatteryLocked(boolean enabled) {
@@ -4315,7 +4335,7 @@
}
public boolean isCharging() {
- return mCharging;
+ return isConsideredCharging();
}
public boolean isBatteryNotLow() {
@@ -4326,17 +4346,42 @@
return mMonitor != null;
}
+ public boolean isPowerConnected() {
+ return mPowerConnected;
+ }
+
public int getSeq() {
return mLastBatterySeq;
}
@Override
+ public void onChargingPolicyChanged(int newPolicy) {
+ synchronized (mLock) {
+ if (mChargingPolicy == newPolicy) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Charging policy changed from " + mChargingPolicy + " to " + newPolicy);
+ }
+
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mChargingPolicy = newPolicy;
+
+ if (isConsideredCharging() != wasConsideredCharging) {
+ for (int c = mControllers.size() - 1; c >= 0; --c) {
+ mControllers.get(c).onBatteryStateChangedLocked();
+ }
+ }
+ }
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
onReceiveInternal(intent);
}
- @VisibleForTesting
- public void onReceiveInternal(Intent intent) {
+ private void onReceiveInternal(Intent intent) {
synchronized (mLock) {
final String action = intent.getAction();
boolean changed = false;
@@ -4356,21 +4401,49 @@
mBatteryNotLow = true;
changed = true;
}
+ } else if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Battery level changed @ "
+ + sElapsedRealtimeClock.millis());
+ }
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ changed = isConsideredCharging() != wasConsideredCharging;
+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (mPowerConnected) {
+ return;
+ }
+ mPowerConnected = true;
+ changed = true;
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (!mPowerConnected) {
+ return;
+ }
+ mPowerConnected = false;
+ changed = true;
} else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
}
if (!mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = true;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
}
if (mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = false;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
}
mLastBatterySeq =
@@ -4382,6 +4455,30 @@
}
}
}
+
+ private boolean isConsideredCharging() {
+ if (mCharging) {
+ return true;
+ }
+ // BatteryService (or Health HAL or whatever central location makes sense)
+ // should ideally hold this logic so that everyone has a consistent
+ // idea of when the device is charging (or an otherwise stable charging/plugged state).
+ // TODO(304512874): move this determination to BatteryService
+ if (!mPowerConnected) {
+ return false;
+ }
+
+ if (mChargingPolicy == Integer.MIN_VALUE) {
+ // Property not supported on this device.
+ return false;
+ }
+ // Adaptive charging policies don't expose their target battery level, but 80% is a
+ // commonly used threshold for battery health, so assume that's what's being used by
+ // the policies and use 70%+ as the threshold here for charging in case some
+ // implementations choose to discharge the device slightly before recharging back up
+ // to the target level.
+ return mBatteryLevel >= 70 && BatteryManager.isAdaptiveChargingPolicy(mChargingPolicy);
+ }
}
final class LocalService implements JobSchedulerInternal {
@@ -5450,6 +5547,13 @@
}
}
+ /** Return {@code true} if the device is connected to power. */
+ public boolean isPowerConnected() {
+ synchronized (mLock) {
+ return mBatteryStateTracker.isPowerConnected();
+ }
+ }
+
int getStorageSeq() {
synchronized (mLock) {
return mStorageController.getTracker().getSeq();
@@ -5778,8 +5882,14 @@
mQuotaTracker.dump(pw);
pw.println();
+ pw.print("Power connected: ");
+ pw.println(mBatteryStateTracker.isPowerConnected());
pw.print("Battery charging: ");
- pw.println(mBatteryStateTracker.isCharging());
+ pw.println(mBatteryStateTracker.mCharging);
+ pw.print("Considered charging: ");
+ pw.println(mBatteryStateTracker.isConsideredCharging());
+ pw.print("Battery level: ");
+ pw.println(mBatteryStateTracker.mBatteryLevel);
pw.print("Battery not low: ");
pw.println(mBatteryStateTracker.isBatteryNotLow());
if (mBatteryStateTracker.isMonitoring()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index ddbc2ec..e9f9b14 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -20,12 +20,6 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -36,7 +30,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -60,8 +53,6 @@
@GuardedBy("mLock")
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
- private final PowerTracker mPowerTracker;
-
private final FlexibilityController mFlexibilityController;
/**
* Helper set to avoid too much GC churn from frequent calls to
@@ -77,16 +68,10 @@
public BatteryController(JobSchedulerService service,
FlexibilityController flexibilityController) {
super(service);
- mPowerTracker = new PowerTracker();
mFlexibilityController = flexibilityController;
}
@Override
- public void startTrackingLocked() {
- mPowerTracker.startTracking();
- }
-
- @Override
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasPowerConstraint()) {
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -95,7 +80,7 @@
if (taskStatus.hasChargingConstraint()) {
if (hasTopExemptionLocked(taskStatus)) {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
- mPowerTracker.isPowerConnected());
+ mService.isPowerConnected());
} else {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
mService.isBatteryCharging() && mService.isBatteryNotLow());
@@ -178,7 +163,7 @@
@GuardedBy("mLock")
private void maybeReportNewChargingStateLocked() {
- final boolean powerConnected = mPowerTracker.isPowerConnected();
+ final boolean powerConnected = mService.isPowerConnected();
final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
final boolean batteryNotLow = mService.isBatteryNotLow();
if (DEBUG) {
@@ -239,62 +224,6 @@
mChangedJobs.clear();
}
- private final class PowerTracker extends BroadcastReceiver {
- /**
- * Track whether there is power connected. It doesn't mean the device is charging.
- * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
- * charging.
- */
- private boolean mPowerConnected;
-
- PowerTracker() {
- }
-
- void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- mContext.registerReceiver(this, filter);
-
- // Initialize tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
- }
-
- boolean isPowerConnected() {
- return mPowerConnected;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- final String action = intent.getAction();
-
- if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
- }
- if (mPowerConnected) {
- return;
- }
- mPowerConnected = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
- }
- if (!mPowerConnected) {
- return;
- }
- mPowerConnected = false;
- }
-
- maybeReportNewChargingStateLocked();
- }
- }
- }
-
@VisibleForTesting
ArraySet<JobStatus> getTrackedJobs() {
return mTrackedTasks;
@@ -308,7 +237,6 @@
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
- pw.println("Power connected: " + mPowerTracker.isPowerConnected());
pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
pw.println("Not low: " + mService.isBatteryNotLow());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 2ea980d..a3a686f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -2053,6 +2053,11 @@
case CONSTRAINT_WITHIN_QUOTA:
return JobParameters.STOP_REASON_QUOTA;
+ // This can change from true to false, but should never change when a job is already
+ // running, so there's no reason to log a message or create a new stop reason.
+ case CONSTRAINT_FLEXIBLE:
+ return JobParameters.STOP_REASON_UNDEFINED;
+
// These should never be stop reasons since they can never go from true to false.
case CONSTRAINT_CONTENT_TRIGGER:
case CONSTRAINT_DEADLINE:
diff --git a/api/Android.bp b/api/Android.bp
index 27c372a..9d2147c 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -200,7 +200,7 @@
out: ["current.srcjar"],
cmd: "$(location merge_zips) $(out) $(in)",
srcs: [
- ":api-stubs-docs-non-updatable",
+ ":api-stubs-docs-non-updatable{.exportable}",
":all-modules-public-stubs-source",
],
visibility: ["//visibility:private"], // Used by make module in //development, mind
@@ -361,7 +361,10 @@
previous_api: ":android.api.public.latest",
merge_annotations_dirs: ["metalava-manual"],
defaults_visibility: ["//frameworks/base/api"],
- visibility: ["//frameworks/base/api"],
+ visibility: [
+ "//frameworks/base/api",
+ "//frameworks/base/core/api",
+ ],
}
// We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
diff --git a/api/api.go b/api/api.go
index fa2be21..c733f5b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -130,7 +130,7 @@
Scope string
}
-func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) {
+func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition, stubsTypeSuffix string, doDist bool) {
metalavaCmd := "$(location metalava)"
// Silence reflection warnings. See b/168689341
metalavaCmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
@@ -140,7 +140,7 @@
if txt.Scope != "public" {
filename = txt.Scope + "-" + filename
}
- moduleName := ctx.ModuleName() + "-" + filename
+ moduleName := ctx.ModuleName() + stubsTypeSuffix + filename
props := genruleProps{}
props.Name = proptools.StringPtr(moduleName)
@@ -148,17 +148,19 @@
props.Out = []string{filename}
props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --out $(out)")
props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...)
- props.Dists = []android.Dist{
- {
- Targets: []string{"droidcore"},
- Dir: proptools.StringPtr("api"),
- Dest: proptools.StringPtr(filename),
- },
- {
- Targets: []string{"api_txt", "sdk"},
- Dir: proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"),
- Dest: proptools.StringPtr(txt.DistFilename),
- },
+ if doDist {
+ props.Dists = []android.Dist{
+ {
+ Targets: []string{"droidcore"},
+ Dir: proptools.StringPtr("api"),
+ Dest: proptools.StringPtr(filename),
+ },
+ {
+ Targets: []string{"api_txt", "sdk"},
+ Dir: proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"),
+ Dest: proptools.StringPtr(txt.DistFilename),
+ },
+ }
}
props.Visibility = []string{"//visibility:public"}
ctx.CreateModule(genrule.GenRuleFactory, &props)
@@ -343,7 +345,7 @@
ctx.CreateModule(android.FileGroupFactory, &props)
}
-func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
+func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string, baseTxtModulePrefix, stubsTypeSuffix string, doDist bool) {
var textFiles []MergedTxtDefinition
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
@@ -352,7 +354,7 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
DistFilename: distFilename[i],
- BaseTxt: ":non-updatable-" + f,
+ BaseTxt: ":" + baseTxtModulePrefix + f,
Modules: bootclasspath,
ModuleTag: "{.public" + tagSuffix[i],
Scope: "public",
@@ -360,7 +362,7 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
DistFilename: distFilename[i],
- BaseTxt: ":non-updatable-system-" + f,
+ BaseTxt: ":" + baseTxtModulePrefix + "system-" + f,
Modules: bootclasspath,
ModuleTag: "{.system" + tagSuffix[i],
Scope: "system",
@@ -368,7 +370,7 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
DistFilename: distFilename[i],
- BaseTxt: ":non-updatable-module-lib-" + f,
+ BaseTxt: ":" + baseTxtModulePrefix + "module-lib-" + f,
Modules: bootclasspath,
ModuleTag: "{.module-lib" + tagSuffix[i],
Scope: "module-lib",
@@ -376,14 +378,14 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
DistFilename: distFilename[i],
- BaseTxt: ":non-updatable-system-server-" + f,
+ BaseTxt: ":" + baseTxtModulePrefix + "system-server-" + f,
Modules: system_server_classpath,
ModuleTag: "{.system-server" + tagSuffix[i],
Scope: "system-server",
})
}
for _, txt := range textFiles {
- createMergedTxt(ctx, txt)
+ createMergedTxt(ctx, txt, stubsTypeSuffix, doDist)
}
}
@@ -465,7 +467,8 @@
bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
sort.Strings(bootclasspath)
}
- createMergedTxts(ctx, bootclasspath, system_server_classpath)
+ createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-", "-", false)
+ createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-exportable-", "-exportable-", true)
createMergedPublicStubs(ctx, bootclasspath)
createMergedSystemStubs(ctx, bootclasspath)
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 8d8a82b..77594b7 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,3 +96,54 @@
name: "non-updatable-test-lint-baseline.txt",
srcs: ["test-lint-baseline.txt"],
}
+
+// Exportable stub artifacts
+filegroup {
+ name: "non-updatable-exportable-current.txt",
+ srcs: [":api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-removed.txt",
+ srcs: [":api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-system-current.txt",
+ srcs: [":system-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-system-removed.txt",
+ srcs: [":system-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-module-lib-current.txt",
+ srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-module-lib-removed.txt",
+ srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-test-current.txt",
+ srcs: [":test-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-test-removed.txt",
+ srcs: [":test-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-system-server-current.txt",
+ srcs: [":services-non-updatable-stubs{.exportable.api.txt}"],
+}
+
+filegroup {
+ name: "non-updatable-exportable-system-server-removed.txt",
+ srcs: [":services-non-updatable-stubs{.exportable.removed-api.txt}"],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index cb937d2..bfa486b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -765,7 +765,7 @@
field public static final int endY = 16844051; // 0x1010513
field @Deprecated public static final int endYear = 16843133; // 0x101017d
field public static final int enforceNavigationBarContrast = 16844293; // 0x1010605
- field public static final int enforceStatusBarContrast = 16844292; // 0x1010604
+ field @Deprecated public static final int enforceStatusBarContrast = 16844292; // 0x1010604
field public static final int enterFadeDuration = 16843532; // 0x101030c
field public static final int entries = 16842930; // 0x10100b2
field public static final int entryValues = 16843256; // 0x10101f8
@@ -1196,8 +1196,8 @@
field public static final int multiprocess = 16842771; // 0x1010013
field public static final int name = 16842755; // 0x1010003
field public static final int nativeHeapZeroInitialized = 16844325; // 0x1010625
- field public static final int navigationBarColor = 16843858; // 0x1010452
- field public static final int navigationBarDividerColor = 16844141; // 0x101056d
+ field @Deprecated public static final int navigationBarColor = 16843858; // 0x1010452
+ field @Deprecated public static final int navigationBarDividerColor = 16844141; // 0x101056d
field public static final int navigationContentDescription = 16843969; // 0x10104c1
field public static final int navigationIcon = 16843968; // 0x10104c0
field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1375,6 +1375,7 @@
field public static final int reqTouchScreen = 16843303; // 0x1010227
field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
field public static final int requestRawExternalStorageAccess = 16844357; // 0x1010645
+ field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller;
field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
@@ -1568,7 +1569,7 @@
field public static final int state_single = 16842915; // 0x10100a3
field public static final int state_window_focused = 16842909; // 0x101009d
field public static final int staticWallpaperPreview = 16843569; // 0x1010331
- field public static final int statusBarColor = 16843857; // 0x1010451
+ field @Deprecated public static final int statusBarColor = 16843857; // 0x1010451
field public static final int stepSize = 16843078; // 0x1010146
field public static final int stopWithTask = 16843626; // 0x101036a
field public static final int streamType = 16843273; // 0x1010209
@@ -1890,6 +1891,7 @@
field public static final int windowNoDisplay = 16843294; // 0x101021e
field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
field public static final int windowNoTitle = 16842838; // 0x1010056
+ field @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") public static final int windowOptOutEdgeToEdgeEnforcement;
field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
field public static final int windowReturnTransition = 16843950; // 0x10104ae
@@ -7894,6 +7896,7 @@
field public static final String AUTO_TIME_POLICY = "autoTime";
field public static final String BACKUP_SERVICE_POLICY = "backupService";
field public static final String CAMERA_DISABLED_POLICY = "cameraDisabled";
+ field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
field public static final String LOCK_TASK_POLICY = "lockTask";
field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
@@ -7944,6 +7947,7 @@
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
+ method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public int getContentProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public android.app.admin.PackagePolicy getCredentialManagerPolicy();
method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method @Deprecated public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
@@ -8067,8 +8071,8 @@
method public boolean isUsbDataSignalingEnabled();
method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
- method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow();
- method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow(int);
+ method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow();
+ method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow(int);
method public int logoutUser(@NonNull android.content.ComponentName);
method public void reboot(@NonNull android.content.ComponentName);
method public void removeActiveAdmin(@NonNull android.content.ComponentName);
@@ -8100,6 +8104,7 @@
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, conditional=true) public void setCommonCriteriaModeEnabled(@Nullable android.content.ComponentName, boolean);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI, conditional=true) public void setConfiguredNetworksLockdownState(@Nullable android.content.ComponentName, boolean);
+ method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public void setContentProtectionPolicy(@Nullable android.content.ComponentName, int);
method public void setCredentialManagerPolicy(@Nullable android.app.admin.PackagePolicy);
method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method @Deprecated public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
@@ -8525,6 +8530,7 @@
field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452
field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451
field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
+ field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c
field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477
field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478
field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472
@@ -16525,8 +16531,8 @@
field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
}
@@ -18003,7 +18009,7 @@
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -24706,6 +24712,7 @@
}
public class Ringtone {
+ method protected void finalize();
method public android.media.AudioAttributes getAudioAttributes();
method @Deprecated public int getStreamType();
method public String getTitle(android.content.Context);
@@ -26632,6 +26639,8 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+ field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONESHOT = 3; // 0x3
+ field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONEWAY = 2; // 0x2
field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
}
@@ -27305,6 +27314,24 @@
field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1
field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2
field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON = "video_unavailable_reason";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
field public static final int SIGNAL_STRENGTH_LOST = 1; // 0x1
field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
@@ -27420,6 +27447,7 @@
method public void notifyTuned(@NonNull android.net.Uri);
method public void notifyTvMessage(int, @NonNull android.os.Bundle);
method public void notifyVideoAvailable();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
method public void notifyVideoUnavailable(int);
method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer);
method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27456,8 +27484,10 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void onTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
method public void onTvMessage(int, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void sendTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
method public void setOverlayViewEnabled(boolean);
}
@@ -27588,6 +27618,7 @@
method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
method public void setTvMessageEnabled(int, boolean);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setVideoFrozen(boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
@@ -27631,6 +27662,7 @@
method public void onTuned(@NonNull String, @NonNull android.net.Uri);
method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle);
method public void onVideoAvailable(String);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(@NonNull String, boolean);
method public void onVideoSizeChanged(String, int, int);
method public void onVideoUnavailable(String, int);
}
@@ -27641,6 +27673,27 @@
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
+ method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+ method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+ method public void unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+ field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.ad.action.APP_LINK_COMMAND";
+ field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+ field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+ field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+ field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+ field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+ field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+ field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+ field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+ field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+ field public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+ field public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+ field public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+ field public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+ field public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+ field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+ field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+ field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
}
public abstract static class TvAdManager.TvAdServiceCallback {
@@ -27652,6 +27705,7 @@
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
ctor public TvAdService();
+ method public void onAppLinkCommand(@NonNull android.os.Bundle);
method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
@@ -27660,17 +27714,26 @@
public abstract static class TvAdService.Session implements android.view.KeyEvent.Callback {
ctor public TvAdService.Session(@NonNull android.content.Context);
+ method public boolean isMediaViewEnabled();
method @CallSuper public void layoutSurface(int, int, int, int);
+ method @Nullable public android.view.View onCreateMediaView();
method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, @Nullable android.view.KeyEvent);
method public boolean onKeyUp(int, @Nullable android.view.KeyEvent);
+ method public void onMediaViewSizeChanged(@Px int, @Px int);
method public abstract void onRelease();
+ method public void onResetAdService();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+ method public void onStartAdService();
+ method public void onStopAdService();
method public void onSurfaceChanged(int, int, int);
method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+ method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+ method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
+ method @CallSuper public void setMediaViewEnabled(boolean);
}
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdServiceInfo implements android.os.Parcelable {
@@ -27687,12 +27750,26 @@
ctor public TvAdView(@NonNull android.content.Context);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+ method public void clearOnUnhandledInputEventListener();
+ method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
+ method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
method public void onMeasure(int, int);
+ method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
method public void onVisibilityChanged(@NonNull android.view.View, int);
method public void prepareAdService(@NonNull String, @NonNull String);
+ method public void reset();
+ method public void resetAdService();
+ method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+ method public boolean setTvView(@Nullable android.media.tv.TvView);
+ method public void startAdService();
+ method public void stopAdService();
+ }
+
+ public static interface TvAdView.OnUnhandledInputEventListener {
+ method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
}
}
@@ -27805,6 +27882,7 @@
method public void onAdResponse(@NonNull android.media.tv.AdResponse);
method public void onAvailableSpeeds(@NonNull float[]);
method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
method public void onContentAllowed();
method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
method public void onCreateBiInteractiveAppRequest(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -27854,11 +27932,13 @@
method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void onVideoAvailable();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(boolean);
method public void onVideoUnavailable(int);
method @CallSuper public void removeBroadcastInfo(int);
method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
method @CallSuper public void requestAvailableSpeeds();
method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestCertificate(@NonNull String, int);
method @CallSuper public void requestCurrentChannelLcn();
method @CallSuper public void requestCurrentChannelUri();
method @CallSuper public void requestCurrentTvInputId();
@@ -27867,6 +27947,7 @@
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
method @CallSuper public void requestStopRecording(@NonNull String);
method @CallSuper public void requestStreamVolume();
@@ -27916,6 +27997,7 @@
method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
method public void notifyTimeShiftStatusChanged(@NonNull String, int);
method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
@@ -27926,6 +28008,7 @@
method public void reset();
method public void resetInteractiveApp();
method public void sendAvailableSpeeds(@NonNull float[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
method public void sendCurrentChannelLcn(int);
method public void sendCurrentChannelUri(@Nullable android.net.Uri);
method public void sendCurrentTvInputId(@Nullable String);
@@ -27960,6 +28043,7 @@
method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
method public void onRequestAvailableSpeeds(@NonNull String);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestCertificate(@NonNull String, @NonNull String, int);
method public void onRequestCurrentChannelLcn(@NonNull String);
method public void onRequestCurrentChannelUri(@NonNull String);
method public void onRequestCurrentTvInputId(@NonNull String);
@@ -27968,6 +28052,7 @@
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
method public void onRequestStopRecording(@NonNull String, @NonNull String);
method public void onRequestStreamVolume(@NonNull String);
@@ -46220,6 +46305,7 @@
method @NonNull public android.telephony.euicc.EuiccManager createForCardId(int);
method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void deleteSubscription(int, android.app.PendingIntent);
method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent);
+ method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public long getAvailableMemoryInBytes();
method @Nullable public String getEid();
method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
method public boolean isEnabled();
@@ -46252,6 +46338,7 @@
field public static final int ERROR_SIM_MISSING = 10008; // 0x2718
field public static final int ERROR_TIME_OUT = 10005; // 0x2715
field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717
+ field @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L; // 0xffffffffffffffffL
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION";
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE";
@@ -47225,7 +47312,7 @@
method public int getLineForOffset(int);
method public int getLineForVertical(int);
method public float getLineLeft(int);
- method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
+ method @FlaggedApi("com.android.text.flags.letter_spacing_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
method public float getLineMax(int);
method public float getLineRight(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47276,7 +47363,7 @@
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -53540,8 +53627,8 @@
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
method public android.media.session.MediaController getMediaController();
- method @ColorInt public abstract int getNavigationBarColor();
- method @ColorInt public int getNavigationBarDividerColor();
+ method @Deprecated @ColorInt public abstract int getNavigationBarColor();
+ method @Deprecated @ColorInt public int getNavigationBarDividerColor();
method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
method public android.transition.Transition getReenterTransition();
method public android.transition.Transition getReturnTransition();
@@ -53551,7 +53638,7 @@
method public android.transition.Transition getSharedElementReenterTransition();
method public android.transition.Transition getSharedElementReturnTransition();
method public boolean getSharedElementsUseOverlay();
- method @ColorInt public abstract int getStatusBarColor();
+ method @Deprecated @ColorInt public abstract int getStatusBarColor();
method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
method public long getTransitionBackgroundFadeDuration();
method public android.transition.TransitionManager getTransitionManager();
@@ -53567,7 +53654,7 @@
method public abstract boolean isFloating();
method public boolean isNavigationBarContrastEnforced();
method public abstract boolean isShortcutKey(int, android.view.KeyEvent);
- method public boolean isStatusBarContrastEnforced();
+ method @Deprecated public boolean isStatusBarContrastEnforced();
method public boolean isWideColorGamut();
method public final void makeActive();
method protected abstract void onActive();
@@ -53599,7 +53686,7 @@
method public abstract void setContentView(android.view.View);
method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method public abstract void setDecorCaptionShade(int);
- method public void setDecorFitsSystemWindows(boolean);
+ method @Deprecated public void setDecorFitsSystemWindows(boolean);
method protected void setDefaultWindowFormat(int);
method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
method public void setDimAmount(float);
@@ -53621,9 +53708,9 @@
method public void setLocalFocus(boolean, boolean);
method public void setLogo(@DrawableRes int);
method public void setMediaController(android.media.session.MediaController);
- method public abstract void setNavigationBarColor(@ColorInt int);
+ method @Deprecated public abstract void setNavigationBarColor(@ColorInt int);
method public void setNavigationBarContrastEnforced(boolean);
- method public void setNavigationBarDividerColor(@ColorInt int);
+ method @Deprecated public void setNavigationBarDividerColor(@ColorInt int);
method public void setPreferMinimalPostProcessing(boolean);
method public void setReenterTransition(android.transition.Transition);
method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
@@ -53635,8 +53722,8 @@
method public void setSharedElementReturnTransition(android.transition.Transition);
method public void setSharedElementsUseOverlay(boolean);
method public void setSoftInputMode(int);
- method public abstract void setStatusBarColor(@ColorInt int);
- method public void setStatusBarContrastEnforced(boolean);
+ method @Deprecated public abstract void setStatusBarColor(@ColorInt int);
+ method @Deprecated public void setStatusBarContrastEnforced(boolean);
method public void setSustainedPerformanceMode(boolean);
method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
method public abstract void setTitle(CharSequence);
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 162f54c..e901f00 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -389,6 +389,12 @@
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+KotlinOperator: android.graphics.Matrix44#get(int, int):
+ Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+KotlinOperator: android.graphics.Matrix44#set(int, int, float):
+ Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
@@ -477,6 +483,8 @@
Method 'clearResetPasswordToken' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(android.content.ComponentName, String, android.security.keystore.KeyGenParameterSpec, int):
Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+ Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -513,6 +521,8 @@
Method 'removeCrossProfileWidgetProvider' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+ Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
Method 'setLockTaskFeatures' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskPackages(android.content.ComponentName, String[]):
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
index a2179bc..42c4efc 100644
--- a/core/api/module-lib-lint-baseline.txt
+++ b/core/api/module-lib-lint-baseline.txt
@@ -611,6 +611,8 @@
Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+ Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -657,6 +659,8 @@
Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+ Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f36d560..a942e0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11,6 +11,7 @@
field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
+ field @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES_FULL = "android.permission.ACCESS_HIDDEN_PROFILES_FULL";
field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
@@ -3150,14 +3151,38 @@
package android.app.wearable {
+ @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public final class WearableSensingDataRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDataSize();
+ method public int getDataType();
+ method public static int getMaxRequestSize();
+ method public static int getRateLimit();
+ method @NonNull public static java.time.Duration getRateLimitWindowSize();
+ method @NonNull public android.os.PersistableBundle getRequestDetails();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wearable.WearableSensingDataRequest> CREATOR;
+ }
+
+ @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final class WearableSensingDataRequest.Builder {
+ ctor public WearableSensingDataRequest.Builder(int);
+ method @NonNull public android.app.wearable.WearableSensingDataRequest build();
+ method @NonNull public android.app.wearable.WearableSensingDataRequest.Builder setRequestDetails(@NonNull android.os.PersistableBundle);
+ }
+
public class WearableSensingManager {
+ method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+ field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
}
@@ -4249,7 +4274,6 @@
public final class UserProperties implements android.os.Parcelable {
method public int describeContents();
method public int getCrossProfileContentSharingStrategy();
- method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility();
method public int getShowInQuietMode();
method public int getShowInSharingSurfaces();
method public boolean isCredentialShareableWithParent();
@@ -4259,9 +4283,6 @@
field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
- field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1
- field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff
- field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0
field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
@@ -4415,8 +4436,9 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+ ctor public CancelSelectionRequest(@NonNull android.credentials.selection.RequestToken, boolean, @NonNull String);
method public int describeContents();
- method @NonNull public String getAppPackageName();
+ method @NonNull public String getPackageName();
method @NonNull public android.credentials.selection.RequestToken getRequestToken();
method public boolean shouldShowCancellationExplanation();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -4498,10 +4520,10 @@
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public String getAppPackageName();
method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
+ method @NonNull public String getPackageName();
method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
method @NonNull public android.credentials.selection.RequestToken getRequestToken();
method @NonNull public String getType();
@@ -6954,6 +6976,8 @@
@FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+ method public static long getDefaultFadeInDurationMillis();
+ method public static long getDefaultFadeOutDurationMillis();
method public long getFadeInDelayForOffenders();
method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
method public long getFadeInDurationForUsage(int);
@@ -6979,7 +7003,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
field public static final long DURATION_NOT_SET = 0L; // 0x0L
field public static final int FADE_STATE_DISABLED = 0; // 0x0
- field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
field public static final String TAG = "FadeManagerConfiguration";
field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
@@ -6994,10 +7017,10 @@
method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
method @NonNull public android.media.FadeManagerConfiguration build();
- method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
- method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
- method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
- method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsages();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentTypes();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUids();
method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
@@ -12404,6 +12427,7 @@
method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
method @Deprecated public abstract int onEraseSubscriptions(int);
method public int onEraseSubscriptions(int, int);
+ method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public long onGetAvailableMemoryInBytes(int);
method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean);
method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean);
method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean);
@@ -13427,12 +13451,24 @@
package android.service.wearable {
+ @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public interface WearableSensingDataRequester {
+ method public void requestData(@NonNull android.app.wearable.WearableSensingDataRequest, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ field public static final int STATUS_OBSERVER_CANCELLED = 2; // 0x2
+ field public static final int STATUS_SUCCESS = 1; // 0x1
+ field public static final int STATUS_TOO_FREQUENT = 4; // 0x4
+ field public static final int STATUS_TOO_LARGE = 3; // 0x3
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
public abstract class WearableSensingService extends android.app.Service {
ctor public WearableSensingService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverRegistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
method public abstract void onStopDetection(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
@@ -13608,6 +13644,13 @@
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
+ public final class DisconnectCause implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public DisconnectCause(int, @NonNull CharSequence, @NonNull CharSequence, @NonNull String, int, int, int, @Nullable android.telephony.ims.ImsReasonInfo);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable public android.telephony.ims.ImsReasonInfo getImsReasonInfo();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyDisconnectCause();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyPreciseDisconnectCause();
+ }
+
public abstract class InCallService extends android.app.Service {
method @Deprecated public android.telecom.Phone getPhone();
method @Deprecated public void onPhoneCreated(android.telecom.Phone);
@@ -14180,12 +14223,12 @@
method @NonNull public android.telephony.DataThrottlingRequest.Builder setDataThrottlingAction(int);
}
- @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public class DomainSelectionService extends android.app.Service {
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public abstract class DomainSelectionService extends android.app.Service {
ctor public DomainSelectionService();
method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo);
- method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @NonNull public java.util.concurrent.Executor onCreateExecutor();
- method public void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
+ method public abstract void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
method public void onServiceStateUpdated(int, int, @NonNull android.telephony.ServiceState);
field public static final int SCAN_TYPE_FULL_SERVICE = 2; // 0x2
field public static final int SCAN_TYPE_LIMITED_SERVICE = 1; // 0x1
@@ -14199,7 +14242,7 @@
method @Nullable public android.net.Uri getAddress();
method @Nullable public String getCallId();
method public int getCsDisconnectCause();
- method @Nullable public android.telephony.EmergencyRegResult getEmergencyRegResult();
+ method @Nullable public android.telephony.EmergencyRegistrationResult getEmergencyRegistrationResult();
method @Nullable public android.telephony.ims.ImsReasonInfo getPsDisconnectCause();
method public int getSelectorType();
method public int getSlotIndex();
@@ -14215,13 +14258,13 @@
@FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes.Builder {
ctor public DomainSelectionService.SelectionAttributes.Builder(int, int, int);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes build();
- method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@NonNull android.net.Uri);
- method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@NonNull String);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@Nullable android.net.Uri);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@Nullable String);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCsDisconnectCause(int);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergency(boolean);
- method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegResult(@NonNull android.telephony.EmergencyRegResult);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegistrationResult(@Nullable android.telephony.EmergencyRegistrationResult);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setExitedFromAirplaneMode(boolean);
- method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@NonNull android.telephony.ims.ImsReasonInfo);
+ method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@Nullable android.telephony.ims.ImsReasonInfo);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setTestEmergencyNumber(boolean);
method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setVideoCall(boolean);
}
@@ -14231,7 +14274,7 @@
method public void reselectDomain(@NonNull android.telephony.DomainSelectionService.SelectionAttributes);
}
- @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegResult implements android.os.Parcelable {
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegistrationResult implements android.os.Parcelable {
method public int describeContents();
method public int getAccessNetwork();
method @NonNull public String getCountryIso();
@@ -14244,7 +14287,7 @@
method public boolean isEmcBearerSupported();
method public boolean isVopsSupported();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegResult> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegistrationResult> CREATOR;
}
public final class ImsiEncryptionInfo implements android.os.Parcelable {
@@ -14757,6 +14800,7 @@
method public boolean areUiccApplicationsEnabled();
method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
method public int getProfileClass();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public int getTransferStatus();
method public boolean isGroupDisabled();
}
@@ -14779,6 +14823,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setTransferStatus(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -14788,6 +14833,9 @@
field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_CONVERTED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1; // 0x1
field @NonNull public static final android.net.Uri VT_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -15311,7 +15359,7 @@
@FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface WwanSelectorCallback {
method public void onDomainSelected(int, boolean);
- method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegResult>);
+ method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegistrationResult>);
}
}
@@ -15653,7 +15701,9 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isPsimConversionSupported(int);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setPsimConversionSupportedCarriers(@NonNull java.util.Set<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
@@ -17377,6 +17427,19 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public class EnableRequestAttributes {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isDemoMode();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEmergencyMode();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEnabled();
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final class EnableRequestAttributes.Builder {
+ ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public EnableRequestAttributes.Builder(boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes build();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setDemoMode(boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setEmergencyMode(boolean);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
@@ -17442,10 +17505,11 @@
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(@NonNull android.telephony.satellite.EnableRequestAttributes, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEmergencyModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -17507,6 +17571,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 6c83fd0..8a485d2 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -525,6 +525,10 @@
Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords
+KotlinOperator: android.hardware.camera2.extension.CharacteristicsMap#get(String):
+ Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #1:
Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`)
ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #2:
@@ -685,6 +689,8 @@
Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+ Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -731,6 +737,8 @@
Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+ Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
@@ -2305,14 +2313,14 @@
New API must be flagged with @FlaggedApi: constructor android.telephony.satellite.SatelliteManager.SatelliteException(int)
UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException#getErrorCode():
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.SatelliteException.getErrorCode()
-UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback:
- New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
-UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
- New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback:
New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteModemStateCallback
UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback#onSatelliteModemStateChanged(int):
New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteModemStateCallback.onSatelliteModemStateChanged(int)
+UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback:
+ New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
+UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
+ New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback:
New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteTransmissionUpdateCallback
UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback#onReceiveDatagramStateChanged(int, int, int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4a048bd..fc095d4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -624,6 +624,7 @@
field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
field public static final int OPERATION_SET_CAMERA_DISABLED = 31; // 0x1f
+ field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; // 0x29
field public static final int OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY = 32; // 0x20
field public static final int OPERATION_SET_GLOBAL_PRIVATE_DNS = 33; // 0x21
field public static final int OPERATION_SET_KEEP_UNINSTALLED_PACKAGES = 17; // 0x11
@@ -1282,10 +1283,6 @@
field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0
}
- @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
- ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public CancelSelectionRequest(@NonNull android.os.IBinder, boolean, @NonNull String);
- }
-
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry);
method @Nullable public android.credentials.selection.Entry getRemoteEntry();
@@ -1882,6 +1879,7 @@
method @FlaggedApi("android.media.audio.focus_freeze_test_api") @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest();
method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
+ method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value();
method public int getStreamMinVolumeInt(int);
@@ -2055,6 +2053,10 @@
package android.media.audiopolicy {
+ public class AudioPolicy {
+ method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getMixes();
+ }
+
public static class AudioPolicy.Builder {
method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean);
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 5e904ef9..b938f0f 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -673,6 +673,8 @@
Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+ Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -721,6 +723,8 @@
Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+ Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceOwner(android.content.ComponentName, int):
Method 'setDeviceOwner' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 084c71f..a8d183a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5954,6 +5954,20 @@
}
/**
+ * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color
+ * palette readiness.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY)
+ public void setThemeOverlayReady(boolean readiness) {
+ try {
+ getService().setThemeOverlayReady(readiness);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resets the state of the {@link com.android.server.am.AppErrors} instance.
* This is intended for use with CTS only.
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 232fc92..0ae2e01 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1258,4 +1258,11 @@
*/
public abstract boolean clearApplicationUserData(String packageName, boolean keepState,
boolean isRestore, IPackageDataObserver observer, int userId);
+
+ /**
+ * Returns current state of {@link com.android.systemui.theme.ThemeOverlayController} color
+ * palette readiness.
+ * @hide
+ */
+ public abstract boolean getThemeOverlayReadiness();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0dbce97..5074ed7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -4588,19 +4588,19 @@
*/
private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
@UidState int toUidState, @OpFlags int flags) {
- NoteOpEvent lastAccessEvent = null;
+ NoteOpEvent lastRejectEvent = null;
for (AttributedOpEntry attributionEntry : mAttributedOpEntries.values()) {
- NoteOpEvent lastAttributionAccessEvent = attributionEntry.getLastRejectEvent(
+ NoteOpEvent lastAttributionRejectEvent = attributionEntry.getLastRejectEvent(
fromUidState, toUidState, flags);
- if (lastAccessEvent == null || (lastAttributionAccessEvent != null
- && lastAttributionAccessEvent.getNoteTime()
- > lastAccessEvent.getNoteTime())) {
- lastAccessEvent = lastAttributionAccessEvent;
+ if (lastRejectEvent == null || (lastAttributionRejectEvent != null
+ && lastAttributionRejectEvent.getNoteTime()
+ > lastRejectEvent.getNoteTime())) {
+ lastRejectEvent = lastAttributionRejectEvent;
}
}
- return lastAccessEvent;
+ return lastRejectEvent;
}
/**
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b063d04..ceeaf5d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -17,7 +17,6 @@
package android.app;
import android.app.ActivityManager;
-import android.app.ActivityManager.PendingIntentInfo;
import android.app.ActivityTaskManager;
import android.app.ApplicationStartInfo;
import android.app.ApplicationErrorReport;
@@ -553,6 +552,14 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isTopOfTask(in IBinder token);
void bootAnimationComplete();
+
+ /**
+ * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color
+ * palette readiness.
+ * @throws RemoteException
+ */
+ void setThemeOverlayReady(boolean readiness);
+
@UnsupportedAppUsage
void registerTaskStackListener(in ITaskStackListener listener);
void unregisterTaskStackListener(in ITaskStackListener listener);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0a34d36..aa9de81 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,6 +23,7 @@
import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import static java.util.Objects.requireNonNull;
@@ -3540,15 +3541,12 @@
* Sets the token used for background operations for the pending intents associated with this
* notification.
*
- * This token is automatically set during deserialization for you, you usually won't need to
- * call this unless you want to change the existing token, if any.
- *
* @hide
*/
- public void clearAllowlistToken() {
- mAllowlistToken = null;
+ public void overrideAllowlistToken(IBinder token) {
+ mAllowlistToken = token;
if (publicVersion != null) {
- publicVersion.clearAllowlistToken();
+ publicVersion.overrideAllowlistToken(token);
}
}
@@ -5957,7 +5955,7 @@
// there is enough space to do so (and fall back to the left edge if not).
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
- if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "setting evenly divided mode on action list");
}
@@ -6439,7 +6437,7 @@
title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
final CharSequence label = ensureColorSpanContrast(title, p);
- if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "new action layout enabled, gluing instead of setting text");
}
@@ -6463,7 +6461,7 @@
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(buttonFillColor));
if (p.mCallStyleActions) {
- if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
}
@@ -9600,11 +9598,6 @@
/**
* @hide
*/
- public static final boolean USE_NEW_ACTION_LAYOUT = false;
-
- /**
- * @hide
- */
public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
/**
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 6a114f9..60b61f3 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -27,6 +27,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ExponentiallyBucketedHistogram;
import java.util.LinkedList;
@@ -114,6 +115,20 @@
}
/**
+ * Tear down the handler.
+ */
+ @VisibleForTesting
+ public static void resetHandler() {
+ synchronized (sLock) {
+ if (sHandler == null) {
+ return;
+ }
+ sHandler.getLooper().quitSafely();
+ sHandler = null;
+ }
+ }
+
+ /**
* Remove all Messages from the Handler with the given code.
*
* This method intentionally avoids creating the Handler if it doesn't
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index b0bec78..d7aafa0 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -22,7 +22,6 @@
import android.app.admin.flags.Flags;
import android.os.UserManager;
-
import java.util.Objects;
/**
@@ -163,6 +162,12 @@
public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider";
/**
+ * String identifier for {@link DevicePolicyManager#setContentProtectionPolicy}.
+ */
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
+
+ /**
* String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
*/
@FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 86d0125..c8762c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,12 +18,14 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.LOCK_DEVICE;
import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS;
@@ -53,7 +55,6 @@
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -3825,6 +3826,10 @@
/** @hide */
@TestApi
public static final int OPERATION_UNINSTALL_CA_CERT = 40;
+ /** @hide */
+ @TestApi
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41;
private static final String PREFIX_OPERATION = "OPERATION_";
@@ -3869,7 +3874,8 @@
OPERATION_SET_PERMISSION_GRANT_STATE,
OPERATION_SET_PERMISSION_POLICY,
OPERATION_SET_RESTRICTIONS_PROVIDER,
- OPERATION_UNINSTALL_CA_CERT
+ OPERATION_UNINSTALL_CA_CERT,
+ OPERATION_SET_CONTENT_PROTECTION_POLICY
})
@Retention(RetentionPolicy.SOURCE)
public static @interface DevicePolicyOperation {
@@ -4095,15 +4101,15 @@
}
/** Indicates that content protection is not controlled by policy, allowing user to choose. */
- @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0;
- /** Indicates that content protection is controlled and disabled by a policy. */
- @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ /** Indicates that content protection is controlled and disabled by a policy (default). */
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
public static final int CONTENT_PROTECTION_DISABLED = 1;
/** Indicates that content protection is controlled and enabled by a policy. */
- @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
public static final int CONTENT_PROTECTION_ENABLED = 2;
/** @hide */
@@ -4118,6 +4124,86 @@
public @interface ContentProtectionPolicy {}
/**
+ * Sets the content protection policy which controls scanning for deceptive apps.
+ * <p>
+ * This function can only be called by the device owner, a profile owner of an affiliated user
+ * or profile, or the profile owner when no device owner is set or holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. See
+ * {@link #isAffiliatedUser}.
+ * Any policy set via this method will be cleared if the user becomes unaffiliated.
+ * <p>
+ * After the content protection policy has been set,
+ * {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not.
+ * This callback will contain:
+ * <ul>
+ * <li> The policy identifier {@link DevicePolicyIdentifiers#CONTENT_PROTECTION_POLICY}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+ * caller is not a device admin.
+ * @param policy The content protection policy to set. One of {@link
+ * #CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY},
+ * {@link #CONTENT_PROTECTION_DISABLED} or {@link #CONTENT_PROTECTION_ENABLED}.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+ * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+ * @see #isAffiliatedUser
+ */
+ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+ @SupportsCoexistence
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ public void setContentProtectionPolicy(
+ @Nullable ComponentName admin, @ContentProtectionPolicy int policy) {
+ throwIfParentInstance("setContentProtectionPolicy");
+ if (mService != null) {
+ try {
+ mService.setContentProtectionPolicy(admin, mContext.getPackageName(), policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the current content protection policy.
+ * <p>
+ * The returned policy will be the current resolved policy rather than the policy set by the
+ * calling admin.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+ * caller is not a device admin.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+ * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+ * @see #isAffiliatedUser
+ * @see #setContentProtectionPolicy
+ */
+ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+ public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
+ throwIfParentInstance("getContentProtectionPolicy");
+ if (mService != null) {
+ try {
+ return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return CONTENT_PROTECTION_DISABLED;
+ }
+
+ /**
* This object is a single place to tack on invalidation and disable calls. All
* binder caches in this class derive from this Config, so all can be invalidated or
* disabled through this Config.
@@ -6330,10 +6416,10 @@
* (PIN, pattern, or password). This API is intended for use only by device admins.
* <p>
* From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have
- * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is
- * true, then the method will return without completing any action. Before version
- * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature,
- * regardless of the caller's permissions.
+ * the LOCK_DEVICE permission or the device must have the
+ * device admin feature; if neither is true, then the method will return without completing
+ * any action. Before version {@link android.os.Build.VERSION_CODES#R},
+ * the device needed the device admin feature, regardless of the caller's permissions.
* <p>
* The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
* to be able to call this method; if it has not, a security exception will be thrown.
@@ -6353,7 +6439,8 @@
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
*/
- @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
+ @SuppressLint("RequiresPermission")
+ @RequiresPermission(value = LOCK_DEVICE, conditional = true)
public void lockNow() {
lockNow(0);
}
@@ -6364,14 +6451,13 @@
* <p>
* This method secures the device in response to an urgent situation, such as a lost or stolen
* device. After this method is called, the device must be unlocked using strong authentication
- * (PIN, pattern, or password). This API is for use only by device admins and holders of the
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission.
+ * (PIN, pattern, or password). This API is intended for use only by device admins.
* <p>
* From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have
- * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is
- * true, then the method will return without completing any action. Before version
- * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature,
- * regardless of the caller's permissions.
+ * the LOCK_DEVICE permission or the device must have the
+ * device admin feature; if neither is true, then the method will return without completing any
+ * action. Before version {@link android.os.Build.VERSION_CODES#R}, the device needed the device
+ * admin feature, regardless of the caller's permissions.
* <p>
* A calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
* to be able to call this method; if it has not, a security exception will be thrown.
@@ -6400,7 +6486,7 @@
* @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the does not hold
- * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission, or
+ * the {@link android.Manifest.permission#LOCK_DEVICE} permission, or
* the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is passed by an
* application that is not a profile owner of a managed profile.
* @throws IllegalArgumentException if the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is
@@ -6409,7 +6495,7 @@
* flag is passed when {@link #getStorageEncryptionStatus} does not return
* {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}.
*/
- @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
+ @RequiresPermission(value = LOCK_DEVICE, conditional = true)
public void lockNow(@LockNowFlag int flags) {
if (mService != null) {
try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 575fa4c..efcf563 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -610,4 +610,7 @@
String getFinancedDeviceKioskRoleHolder(String callerPackageName);
void calculateHasIncompatibleAccounts();
+
+ void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
+ int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
}
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index ca2e97e..ed1b8ca 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -17,12 +17,14 @@
package android.app.admin;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Build;
@@ -99,6 +101,7 @@
TAG_PACKAGE_INSTALLED,
TAG_PACKAGE_UPDATED,
TAG_PACKAGE_UNINSTALLED,
+ TAG_BACKUP_SERVICE_TOGGLED,
})
public @interface SecurityLogTag {}
@@ -599,6 +602,18 @@
public static final int TAG_PACKAGE_UNINSTALLED = SecurityLogTags.SECURITY_PACKAGE_UNINSTALLED;
/**
+ * Indicates that an admin has enabled or disabled backup service. The log entry contains the
+ * following information about the event encapsulated in an {@link Object} array, accessible
+ * via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
+ * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
+ */
+ @FlaggedApi(Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
+ public static final int TAG_BACKUP_SERVICE_TOGGLED =
+ SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+ /**
* Event severity level indicating that the event corresponds to normal workflow.
*/
public static final int LEVEL_INFO = 1;
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index e4af8dd..7b3aa7b 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -47,4 +47,5 @@
210040 security_bluetooth_disconnection (addr|3),(reason|3)
210041 security_package_installed (package_name|3),(version_code|1),(user_id|1)
210042 security_package_updated (package_name|3),(version_code|1),(user_id|1)
-210043 security_package_uninstalled (package_name|3),(version_code|1),(user_id|1)
\ No newline at end of file
+210043 security_package_uninstalled (package_name|3),(version_code|1),(user_id|1)
+210044 security_backup_service_toggled (package|3),(admin_user|1),(enabled|1)
\ No newline at end of file
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index b3ecd92..561eb00 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -62,3 +62,10 @@
description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
bug: "309183330"
}
+
+flag {
+ name: "backup_service_security_log_event_enabled"
+ namespace: "enterprise"
+ description: "Emit a security log event when DPM.setBackupServiceEnabled is called"
+ bug: "304999634"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index c40b23e..274d02a 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -56,4 +56,14 @@
namespace: "systemui"
description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels"
bug: "241732519"
+}
+
+flag {
+ name: "evenly_divided_call_style_action_layout"
+ namespace: "systemui"
+ description: "Evenly divides horizontal space for action buttons in CallStyle notifications."
+ bug: "268733030"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index ff37bd8..3cbc8a2 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -16,6 +16,7 @@
package android.app.wearable;
+import android.app.PendingIntent;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -28,7 +29,13 @@
*/
interface IWearableSensingManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void registerDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void unregisterDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingDataRequest.java b/core/java/android/app/wearable/WearableSensingDataRequest.java
new file mode 100644
index 0000000..9329b37
--- /dev/null
+++ b/core/java/android/app/wearable/WearableSensingDataRequest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wearable;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import java.time.Duration;
+
+/**
+ * Data class for a data request for wearable sensing.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+@SystemApi
+public final class WearableSensingDataRequest implements Parcelable {
+ private static final int MAX_REQUEST_SIZE = 200;
+ private static final Duration RATE_LIMIT_WINDOW_SIZE = Duration.ofMinutes(1);
+ private static final int RATE_LIMIT = 30;
+
+ private final int mDataType;
+ @NonNull private final PersistableBundle mRequestDetails;
+
+ private WearableSensingDataRequest(int dataType, @NonNull PersistableBundle requestDetails) {
+ mDataType = dataType;
+ mRequestDetails = requestDetails;
+ }
+
+ /** Returns the data type this request is for. */
+ public int getDataType() {
+ return mDataType;
+ }
+
+ /** Returns the details for this request. */
+ @NonNull
+ public PersistableBundle getRequestDetails() {
+ return mRequestDetails;
+ }
+
+ /** Returns the data size of this object when it is parcelled. */
+ public int getDataSize() {
+ Parcel parcel = Parcel.obtain();
+ try {
+ writeToParcel(parcel, describeContents());
+ return parcel.dataSize();
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDataType);
+ dest.writeTypedObject(mRequestDetails, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "WearableSensingDataRequest { "
+ + "dataType = "
+ + mDataType
+ + ", "
+ + "requestDetails = "
+ + mRequestDetails
+ + " }";
+ }
+
+ /**
+ * Returns a String representation of this data request that shows its contents.
+ *
+ * @hide
+ */
+ public String toExpandedString() {
+ if (mRequestDetails != null) {
+ // Trigger unparcelling so that its individual fields will be listed in toString
+ boolean unused =
+ mRequestDetails.getBoolean(
+ "PlaceholderForWearableSensingDataRequest#toExpandedString()");
+ }
+ return toString();
+ }
+
+ /**
+ * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+ *
+ * @hide
+ */
+ public static final String REQUEST_BUNDLE_KEY =
+ "android.app.wearable.WearableSensingDataRequestBundleKey";
+
+ /**
+ * The bundle key for the status callback for a data request, used in {@code
+ * RemoteCallback#sendResult}.
+ *
+ * @hide
+ */
+ public static final String REQUEST_STATUS_CALLBACK_BUNDLE_KEY =
+ "android.app.wearable.WearableSensingDataRequestStatusCallbackBundleKey";
+
+ public static final @NonNull Parcelable.Creator<WearableSensingDataRequest> CREATOR =
+ new Parcelable.Creator<WearableSensingDataRequest>() {
+ @Override
+ public WearableSensingDataRequest[] newArray(int size) {
+ return new WearableSensingDataRequest[size];
+ }
+
+ @Override
+ public WearableSensingDataRequest createFromParcel(@NonNull Parcel in) {
+ int dataType = in.readInt();
+ PersistableBundle requestDetails =
+ in.readTypedObject(PersistableBundle.CREATOR);
+ return new WearableSensingDataRequest(dataType, requestDetails);
+ }
+ };
+
+ /**
+ * Returns the maximum allowed size of a WearableSensingDataRequest when it is parcelled.
+ * Instances that exceed this size can be constructed, but will be rejected by the system when
+ * they leave the isolated WearableSensingService.
+ */
+ public static int getMaxRequestSize() {
+ return MAX_REQUEST_SIZE;
+ }
+
+ /**
+ * Returns the rolling time window used to perform rate limiting on data requests leaving the
+ * WearableSensingService.
+ */
+ @NonNull
+ public static Duration getRateLimitWindowSize() {
+ return RATE_LIMIT_WINDOW_SIZE;
+ }
+
+ /**
+ * Returns the number of data requests allowed to leave the WearableSensingService in each
+ * {@link #getRateLimitWindowSize()}.
+ */
+ public static int getRateLimit() {
+ return RATE_LIMIT;
+ }
+
+ /** A builder for WearableSensingDataRequest. */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ public static final class Builder {
+ private int mDataType;
+ private PersistableBundle mRequestDetails;
+
+ public Builder(int dataType) {
+ mDataType = dataType;
+ }
+
+ /** Sets the request details. */
+ public @NonNull Builder setRequestDetails(@NonNull PersistableBundle requestDetails) {
+ mRequestDetails = requestDetails;
+ return this;
+ }
+
+ /** Builds the WearableSensingDataRequest. */
+ public @NonNull WearableSensingDataRequest build() {
+ if (mRequestDetails == null) {
+ mRequestDetails = PersistableBundle.EMPTY;
+ }
+ return new WearableSensingDataRequest(mDataType, mRequestDetails);
+ }
+ }
+}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index eca0039..077f7b5 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -25,8 +25,11 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
+import android.companion.CompanionDeviceManager;
import android.content.Context;
+import android.content.Intent;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -36,6 +39,8 @@
import android.service.wearable.WearableSensingService;
import android.system.OsConstants;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -55,7 +60,6 @@
*
* @hide
*/
-
@SystemApi
@SystemService(Context.WEARABLE_SENSING_SERVICE)
public class WearableSensingManager {
@@ -68,6 +72,14 @@
public static final String STATUS_RESPONSE_BUNDLE_KEY =
"android.app.wearable.WearableSensingStatusBundleKey";
+ /**
+ * The Intent extra key for the data request in the Intent sent to the PendingIntent registered
+ * with {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_WEARABLE_SENSING_DATA_REQUEST =
+ "android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST";
/**
* An unknown status.
@@ -104,22 +116,54 @@
* The value of the status code that indicates the method called is not supported by the
* implementation of {@link WearableSensingService}.
*/
+
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+ /**
+ * The value of the status code that indicates an error occurred in the encrypted channel backed
+ * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor,
+ * Executor, Consumer)}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ public static final int STATUS_CHANNEL_ERROR = 7;
+
+ /** The value of the status code that indicates the provided data type is not supported. */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8;
+
/** @hide */
- @IntDef(prefix = { "STATUS_" }, value = {
- STATUS_UNKNOWN,
- STATUS_SUCCESS,
- STATUS_UNSUPPORTED,
- STATUS_SERVICE_UNAVAILABLE,
- STATUS_WEARABLE_UNAVAILABLE,
- STATUS_ACCESS_DENIED,
- STATUS_UNSUPPORTED_OPERATION
- })
+ @IntDef(
+ prefix = {"STATUS_"},
+ value = {
+ STATUS_UNKNOWN,
+ STATUS_SUCCESS,
+ STATUS_UNSUPPORTED,
+ STATUS_SERVICE_UNAVAILABLE,
+ STATUS_WEARABLE_UNAVAILABLE,
+ STATUS_ACCESS_DENIED,
+ STATUS_UNSUPPORTED_OPERATION,
+ STATUS_CHANNEL_ERROR,
+ STATUS_UNSUPPORTED_DATA_TYPE
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
+ /**
+ * Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent
+ * provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
+ *
+ * @param intent The Intent received from the PendingIntent.
+ * @return The WearableSensingDataRequest in the provided Intent, or null if the Intent does not
+ * contain a WearableSensingDataRequest.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ @Nullable
+ public static WearableSensingDataRequest getDataRequestFromIntent(@NonNull Intent intent) {
+ return intent.getParcelableExtra(
+ EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class);
+ }
+
private final Context mContext;
private final IWearableSensingManager mService;
@@ -132,6 +176,60 @@
}
/**
+ * Provides a remote wearable device connection to the WearableSensingService and sends the
+ * resulting status to the {@code statusConsumer} after the call.
+ *
+ * <p>This is used by applications that will also provide an implementation of the isolated
+ * WearableSensingService.
+ *
+ * <p>The provided {@code wearableConnection} is expected to be a connection to a remotely
+ * connected wearable device. This {@code wearableConnection} will be attached to
+ * CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int,
+ * InputStream, OutputStream)}, which will create an encrypted channel using {@code
+ * wearableConnection} as the raw underlying connection. The wearable device is expected to
+ * attach its side of the raw connection to its CompanionDeviceManager via the same method so
+ * that the two CompanionDeviceManagers on the two devices can perform attestation and set up
+ * the encrypted channel. Attestation requirements are listed in
+ * com.android.server.security.AttestationVerificationPeerDeviceVerifier
+ *
+ * <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is
+ * referred to as the secureWearableConnection in WearableSensingService. Any data written to
+ * secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw
+ * {@code wearableConnection} to the remote wearable device, which is expected to use its
+ * CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code
+ * wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text
+ * from secureWearableConnection. The raw {@code wearableConnection} provided to this method
+ * will not be directly available to the WearableSensingService.
+ *
+ * <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the
+ * system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer}
+ * and kill the WearableSensingService process.
+ *
+ * <p>Before providing the secureWearableConnection, the system will restart the
+ * WearableSensingService process. Other method calls into WearableSensingService may be dropped
+ * during the restart. The caller is responsible for ensuring other method calls are queued
+ * until a success status is returned from the {@code statusConsumer}.
+ *
+ * @param wearableConnection The connection to provide
+ * @param executor Executor on which to run the consumer callback
+ * @param statusConsumer A consumer that handles the status codes for providing the connection
+ * and errors in the encrypted channel.
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ public void provideWearableConnection(
+ @NonNull ParcelFileDescriptor wearableConnection,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
+ mService.provideWearableConnection(wearableConnection, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Provides a data stream to the WearableSensingService that's backed by the
* parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
* This is used by applications that will also provide an implementation of
@@ -149,15 +247,7 @@
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
try {
- RemoteCallback callback = new RemoteCallback(result -> {
- int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> statusConsumer.accept(status));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- });
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
mService.provideDataStream(parcelFileDescriptor, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -191,19 +281,117 @@
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
try {
- RemoteCallback callback = new RemoteCallback(result -> {
- int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> statusConsumer.accept(status));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- });
+ RemoteCallback callback = createStatusCallback(executor, statusConsumer);
mService.provideData(data, sharedMemory, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Registers a data request observer for the provided data type.
+ *
+ * <p>When data is requested, the provided {@code dataRequestPendingIntent} will be invoked. A
+ * {@link WearableSensingDataRequest} can be extracted from the Intent sent to {@code
+ * dataRequestPendingIntent} by calling {@link #getDataRequestFromIntent(Intent)}. The observer
+ * can then provide the requested data via {@link #provideData(PersistableBundle, SharedMemory,
+ * Executor, Consumer)}.
+ *
+ * <p>There is no limit to the number of observers registered for a data type. How they are
+ * handled depends on the implementation of WearableSensingService.
+ *
+ * <p>When the observer is no longer needed, {@link #unregisterDataRequestObserver(int,
+ * PendingIntent, Executor, Consumer)} should be called with the same {@code
+ * dataRequestPendingIntent}. It should be done regardless of the status code returned from
+ * {@code statusConsumer} in order to clean up housekeeping data for the {@code
+ * dataRequestPendingIntent} maintained by the system.
+ *
+ * <p>Example:
+ *
+ * <pre>{@code
+ * // Create a PendingIntent for MyDataRequestBroadcastReceiver
+ * Intent intent =
+ * new Intent(actionString).setClass(context, MyDataRequestBroadcastReceiver.class);
+ * PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ * context, 0, intent, PendingIntent.FLAG_MUTABLE);
+ *
+ * // Register the PendingIntent as a data request observer
+ * wearableSensingManager.registerDataRequestObserver(
+ * dataType, pendingIntent, executor, statusConsumer);
+ *
+ * // Within MyDataRequestBroadcastReceiver, receive the broadcast Intent and extract the
+ * // WearableSensingDataRequest
+ * {@literal @}Override
+ * public void onReceive(Context context, Intent intent) {
+ * WearableSensingDataRequest dataRequest =
+ * WearableSensingManager.getDataRequestFromIntent(intent);
+ * // After parsing the dataRequest, provide the data
+ * wearableSensingManager.provideData(data, sharedMemory, executor, statusConsumer);
+ * }
+ * }</pre>
+ *
+ * @param dataType The data type to listen to. Values are defined by the application that
+ * implements {@link WearableSensingService}.
+ * @param dataRequestPendingIntent A mutable {@link PendingIntent} that will be invoked when
+ * data is requested. See {@link #getDataRequestFromIntent(Intent)}. Activities are not
+ * allowed to be launched using this PendingIntent.
+ * @param executor Executor on which to run the consumer callback.
+ * @param statusConsumer A consumer that handles the status code for the observer registration.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ public void registerDataRequestObserver(
+ int dataType,
+ @NonNull PendingIntent dataRequestPendingIntent,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
+ mService.registerDataRequestObserver(
+ dataType, dataRequestPendingIntent, statusCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a previously registered data request observer. If the provided {@link
+ * PendingIntent} was not registered, or is already unregistered, the {@link
+ * WearableSensingService} will not be notified.
+ *
+ * @param dataType The data type the observer is for.
+ * @param dataRequestPendingIntent The observer to unregister.
+ * @param executor Executor on which to run the consumer callback.
+ * @param statusConsumer A consumer that handles the status code for the observer
+ * unregistration.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ public void unregisterDataRequestObserver(
+ int dataType,
+ @NonNull PendingIntent dataRequestPendingIntent,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
+ mService.unregisterDataRequestObserver(
+ dataType, dataRequestPendingIntent, statusCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static RemoteCallback createStatusCallback(
+ Executor executor, Consumer<Integer> statusConsumer) {
+ return new RemoteCallback(
+ result -> {
+ int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> statusConsumer.accept(status));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ });
+ }
}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 084cba3..822f02f 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -19,6 +19,9 @@
namespace: "app_widgets"
description: "Move state file IO to non-critical path"
bug: "312949280"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9fe8af5..a64ee5b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -239,6 +239,110 @@
public String requiredDisplayCategory;
/**
+ * Constant corresponding to {@code none} in the
+ * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ * @hide
+ */
+ public static final int CONTENT_URI_PERMISSION_NONE = 0;
+
+ /**
+ * Constant corresponding to {@code read} in the
+ * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ * @hide
+ */
+ public static final int CONTENT_URI_PERMISSION_READ = 1;
+
+ /**
+ * Constant corresponding to {@code write} in the
+ * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ * @hide
+ */
+ public static final int CONTENT_URI_PERMISSION_WRITE = 2;
+
+ /**
+ * Constant corresponding to {@code readOrWrite} in the
+ * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ * @hide
+ */
+ public static final int CONTENT_URI_PERMISSION_READ_OR_WRITE = 3;
+
+ /**
+ * Constant corresponding to {@code readAndWrite} in the
+ * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ * @hide
+ */
+ public static final int CONTENT_URI_PERMISSION_READ_AND_WRITE = 4;
+
+ /** @hide */
+ @SuppressWarnings("SwitchIntDef")
+ public static boolean isRequiredContentUriPermissionRead(
+ @RequiredContentUriPermission int permission) {
+ return switch (permission) {
+ case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+ CONTENT_URI_PERMISSION_READ -> true;
+ default -> false;
+ };
+ }
+
+ /** @hide */
+ @SuppressWarnings("SwitchIntDef")
+ public static boolean isRequiredContentUriPermissionWrite(
+ @RequiredContentUriPermission int permission) {
+ return switch (permission) {
+ case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+ CONTENT_URI_PERMISSION_WRITE -> true;
+ default -> false;
+ };
+ }
+
+ /** @hide */
+ @IntDef(prefix = "CONTENT_URI_PERMISSION_", value = {
+ CONTENT_URI_PERMISSION_NONE,
+ CONTENT_URI_PERMISSION_READ,
+ CONTENT_URI_PERMISSION_WRITE,
+ CONTENT_URI_PERMISSION_READ_OR_WRITE,
+ CONTENT_URI_PERMISSION_READ_AND_WRITE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequiredContentUriPermission {
+ }
+
+ private String requiredContentUriPermissionToFullString(
+ @RequiredContentUriPermission int permission) {
+ return switch (permission) {
+ case CONTENT_URI_PERMISSION_NONE -> "CONTENT_URI_PERMISSION_NONE";
+ case CONTENT_URI_PERMISSION_READ -> "CONTENT_URI_PERMISSION_READ";
+ case CONTENT_URI_PERMISSION_WRITE -> "CONTENT_URI_PERMISSION_WRITE";
+ case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "CONTENT_URI_PERMISSION_READ_OR_WRITE";
+ case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "CONTENT_URI_PERMISSION_READ_AND_WRITE";
+ default -> "unknown=" + permission;
+ };
+ }
+
+ /** @hide */
+ public static String requiredContentUriPermissionToShortString(
+ @RequiredContentUriPermission int permission) {
+ return switch (permission) {
+ case CONTENT_URI_PERMISSION_NONE -> "none";
+ case CONTENT_URI_PERMISSION_READ -> "read";
+ case CONTENT_URI_PERMISSION_WRITE -> "write";
+ case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "read or write";
+ case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "read and write";
+ default -> "unknown=" + permission;
+ };
+ }
+
+ /**
+ * Specifies permissions necessary to launch this activity via
+ * {@link android.content.Context#startActivity} when passing content URIs. The default value is
+ * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
+ * activity invocation based on the invoker's permissions.
+ * @hide
+ */
+ @RequiredContentUriPermission
+ public int requireContentUriPermissionFromCaller;
+
+ /**
* Activity can not be resized and always occupies the fullscreen area with all windows fully
* visible.
* @hide
@@ -1590,6 +1694,7 @@
mMinAspectRatio = orig.mMinAspectRatio;
supportsSizeChanges = orig.supportsSizeChanges;
requiredDisplayCategory = orig.requiredDisplayCategory;
+ requireContentUriPermissionFromCaller = orig.requireContentUriPermissionFromCaller;
}
/**
@@ -1946,6 +2051,11 @@
if (requiredDisplayCategory != null) {
pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory);
}
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "requireContentUriPermissionFromCaller="
+ + requiredContentUriPermissionToFullString(
+ requireContentUriPermissionFromCaller));
+ }
super.dumpBack(pw, prefix, dumpFlags);
}
@@ -1993,6 +2103,7 @@
dest.writeBoolean(supportsSizeChanges);
sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
dest.writeString8(requiredDisplayCategory);
+ dest.writeInt(requireContentUriPermissionFromCaller);
}
/**
@@ -2119,6 +2230,7 @@
mKnownActivityEmbeddingCerts = null;
}
requiredDisplayCategory = source.readString8();
+ requireContentUriPermissionFromCaller = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/IBackgroundInstallControlService.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
index c8e7cae..2e7f19e 100644
--- a/core/java/android/content/pm/IBackgroundInstallControlService.aidl
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -17,10 +17,15 @@
package android.content.pm;
import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
/**
* {@hide}
*/
interface IBackgroundInstallControlService {
ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
+
+ void registerBackgroundInstallCallback(IRemoteCallback callback);
+
+ void unregisterBackgroundInstallCallback(IRemoteCallback callback);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 08f1853..bff90f1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -846,4 +846,6 @@
@EnforcePermission("GET_APP_METADATA")
int getAppMetadataSource(String packageName, int userId);
+
+ ComponentName getDomainVerificationAgent();
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index f54b2ac..d347a0e 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -16,9 +16,6 @@
package android.content.pm;
-import static android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -476,22 +473,26 @@
)
public @interface ProfileApiVisibility {
}
- /*
- * The api visibility value for this profile user is undefined or unknown.
+
+ /**
+ * The api visibility value for this profile user is undefined or unknown.
+ *
+ * @hide
*/
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1;
/**
* Indicates that information about this profile user should be shown in API surfaces.
+ *
+ * @hide
*/
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public static final int PROFILE_API_VISIBILITY_VISIBLE = 0;
/**
* Indicates that information about this profile should be not be visible in API surfaces.
+ *
+ * @hide
*/
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public static final int PROFILE_API_VISIBILITY_HIDDEN = 1;
@@ -555,9 +556,7 @@
setShowInQuietMode(orig.getShowInQuietMode());
setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
- if (android.multiuser.Flags.supportHidingProfiles()) {
- setProfileApiVisibility(orig.getProfileApiVisibility());
- }
+ setProfileApiVisibility(orig.getProfileApiVisibility());
}
/**
@@ -1002,9 +1001,10 @@
/**
* Returns the visibility of the profile user in API surfaces. Any information linked to the
* profile (userId, package names) should be hidden API surfaces if a user is marked as hidden.
+ *
+ * @hide
*/
@NonNull
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public @ProfileApiVisibility int getProfileApiVisibility() {
if (isPresent(INDEX_PROFILE_API_VISIBILITY)) return mProfileApiVisibility;
if (mDefaultProperties != null) return mDefaultProperties.mProfileApiVisibility;
@@ -1012,7 +1012,6 @@
}
/** @hide */
@NonNull
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public void setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility) {
this.mProfileApiVisibility = profileApiVisibility;
setPresent(INDEX_PROFILE_API_VISIBILITY);
@@ -1053,9 +1052,6 @@
@Override
public String toString() {
- String profileApiVisibility =
- android.multiuser.Flags.supportHidingProfiles() ? ", mProfileApiVisibility="
- + getProfileApiVisibility() : "";
// Please print in increasing order of PropertyIndex.
return "UserProperties{"
+ "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
@@ -1079,7 +1075,7 @@
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
- + ", mProfileApiVisibility=" + profileApiVisibility
+ + ", mProfileApiVisibility=" + getProfileApiVisibility()
+ ", mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen()
+ "}";
}
@@ -1114,9 +1110,7 @@
pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible());
pw.println(prefix + " mCrossProfileContentSharingStrategy="
+ getCrossProfileContentSharingStrategy());
- if (android.multiuser.Flags.supportHidingProfiles()) {
- pw.println(prefix + " mProfileApiVisibility=" + getProfileApiVisibility());
- }
+ pw.println(prefix + " mProfileApiVisibility=" + getProfileApiVisibility());
pw.println(prefix + " mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen());
}
@@ -1203,9 +1197,7 @@
setCrossProfileContentSharingStrategy(parser.getAttributeInt(i));
break;
case ATTR_PROFILE_API_VISIBILITY:
- if (android.multiuser.Flags.supportHidingProfiles()) {
- setProfileApiVisibility(parser.getAttributeInt(i));
- }
+ setProfileApiVisibility(parser.getAttributeInt(i));
break;
case ITEMS_RESTRICTED_ON_HOME_SCREEN:
setItemsRestrictedOnHomeScreen(parser.getAttributeBoolean(i));
@@ -1293,10 +1285,8 @@
mCrossProfileContentSharingStrategy);
}
if (isPresent(INDEX_PROFILE_API_VISIBILITY)) {
- if (android.multiuser.Flags.supportHidingProfiles()) {
- serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
- mProfileApiVisibility);
- }
+ serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
+ mProfileApiVisibility);
}
if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
serializer.attributeBoolean(null, ITEMS_RESTRICTED_ON_HOME_SCREEN,
@@ -1566,7 +1556,6 @@
* @hide
*/
@NonNull
- @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
public Builder setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility){
mProfileApiVisibility = profileApiVisibility;
return this;
@@ -1650,9 +1639,7 @@
setDeleteAppWithParent(deleteAppWithParent);
setAlwaysVisible(alwaysVisible);
setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
- if (android.multiuser.Flags.supportHidingProfiles()) {
- setProfileApiVisibility(profileApiVisibility);
- }
+ setProfileApiVisibility(profileApiVisibility);
setItemsRestrictedOnHomeScreen(itemsRestrictedOnHomeScreen);
}
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index f31521d..5e9d8f0 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -206,3 +206,11 @@
description: "Feature flag to enable pre-verified domains"
bug: "307327678"
}
+
+flag {
+ name: "min_target_sdk_24"
+ namespace: "responsible_apis"
+ description: "Feature flag to bump min target sdk to 24"
+ bug: "297603927"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 7ded747..4b890fa 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -109,18 +109,10 @@
}
flag {
- name: "allow_private_profile_apis"
+ name: "enable_private_space_features"
namespace: "profile_experiences"
- description: "Enable only the API changes to support private space"
- bug: "299069460"
-}
-
-flag {
- name: "support_hiding_profiles"
- namespace: "profile_experiences"
- description: "Allow the use of a hide_profile property to hide some profiles behind a permission"
- bug: "316362775"
- is_fixed_read_only: true
+ description: "Enable the support for private space and all its sub-features"
+ bug: "286418785"
}
flag {
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
index 8e28042..6c4fe37 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
@@ -33,7 +33,8 @@
STATE_DENIED,
STATE_LEGACY_FAILURE,
STATE_SYS_CONFIG,
- STATE_FIRST_VERIFIER_DEFINED
+ STATE_PRE_VERIFIED,
+ STATE_FIRST_VERIFIER_DEFINED,
})
@interface State {
}
@@ -92,6 +93,13 @@
int STATE_SYS_CONFIG = 7;
/**
+ * The application has temporarily been granted auto verification for a set of domains as
+ * specified by a trusted installer during the installation. This will treat the domain as
+ * verified, but it should be updated by the verification agent.
+ */
+ int STATE_PRE_VERIFIED = 8;
+
+ /**
* @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED
*/
int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
@@ -115,6 +123,8 @@
return "legacy_failure";
case DomainVerificationState.STATE_SYS_CONFIG:
return "system_configured";
+ case DomainVerificationState.STATE_PRE_VERIFIED:
+ return "pre_verified";
default:
return String.valueOf(state);
}
@@ -135,6 +145,7 @@
case STATE_DENIED:
case STATE_LEGACY_FAILURE:
case STATE_SYS_CONFIG:
+ case STATE_PRE_VERIFIED:
default:
return false;
}
@@ -151,6 +162,7 @@
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_SYS_CONFIG:
+ case DomainVerificationState.STATE_PRE_VERIFIED:
return true;
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_DENIED:
@@ -173,6 +185,7 @@
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
+ case DomainVerificationState.STATE_PRE_VERIFIED:
return true;
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_DENIED:
@@ -194,6 +207,7 @@
case STATE_RESTORED:
case STATE_APPROVED:
case STATE_DENIED:
+ case STATE_PRE_VERIFIED:
return true;
case STATE_NO_RESPONSE:
case STATE_LEGACY_FAILURE:
diff --git a/core/java/android/credentials/Constants.java b/core/java/android/credentials/Constants.java
new file mode 100644
index 0000000..ea30c44
--- /dev/null
+++ b/core/java/android/credentials/Constants.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+/**
+ * Constants for credential manager service that doesn't fit into other structures
+ *
+ * @hide
+ */
+public class Constants {
+ /**
+ * The request is success and user selected an entry
+ */
+ public static final int SUCCESS_CREDMAN_SELECTOR = 0;
+ /**
+ * The error code for ui getting cancelled by user
+ */
+ public static final int FAILURE_CREDMAN_SELECTOR = -1;
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 73361ad..6e53fd9 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -41,8 +41,6 @@
private final PendingIntent mPendingIntent;
- private final GetCredentialResponse mGetCredentialResponse;
-
/**
* @hide
*/
@@ -52,7 +50,6 @@
) {
mCandidateProviderDataList = null;
mPendingIntent = null;
- mGetCredentialResponse = getCredentialResponse;
}
/**
@@ -68,7 +65,6 @@
/*valueName=*/ "candidateProviderDataList");
mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
mPendingIntent = pendingIntent;
- mGetCredentialResponse = null;
}
/**
@@ -85,15 +81,6 @@
*
* @hide
*/
- public GetCredentialResponse getGetCredentialResponse() {
- return mGetCredentialResponse;
- }
-
- /**
- * Returns candidate provider data list.
- *
- * @hide
- */
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@@ -106,14 +93,12 @@
AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
- mGetCredentialResponse = in.readTypedObject(GetCredentialResponse.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(mCandidateProviderDataList);
dest.writeTypedObject(mPendingIntent, flags);
- dest.writeTypedObject(mGetCredentialResponse, flags);
}
@Override
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 1ca11e6..ec46d2f 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -55,3 +55,10 @@
description: "Enables Credential Manager to work with the Biometric Authenticate API"
bug: "323211850"
}
+
+flag {
+ namespace: "wear_frameworks"
+ name: "wear_credential_manager_enabled"
+ description: "Enables Credential Manager on Wear Platform"
+ bug: "301168341"
+}
diff --git a/core/java/android/credentials/selection/CancelSelectionRequest.java b/core/java/android/credentials/selection/CancelSelectionRequest.java
index 2662d76..55acfdb 100644
--- a/core/java/android/credentials/selection/CancelSelectionRequest.java
+++ b/core/java/android/credentials/selection/CancelSelectionRequest.java
@@ -21,7 +21,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -59,7 +58,7 @@
private final boolean mShouldShowCancellationExplanation;
@NonNull
- private final String mAppPackageName;
+ private final String mPackageName;
/**
* Returns the request token matching the user request that should be cancelled.
@@ -85,8 +84,8 @@
* metadata (e.g. "Cancelled by `App Name`").
*/
@NonNull
- public String getAppPackageName() {
- return mAppPackageName;
+ public String getPackageName() {
+ return mPackageName;
}
/**
@@ -98,33 +97,36 @@
return mShouldShowCancellationExplanation;
}
+
/**
* Constructs a {@link CancelSelectionRequest}.
*
- * @hide
+ * @param requestToken request token matching the app request that should be cancelled
+ * @param shouldShowCancellationExplanation whether the UI should display some informational
+ * cancellation message before closing
+ * @param packageName package that is invoking this request
+ *
*/
- @TestApi
- @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
- public CancelSelectionRequest(@NonNull IBinder token, boolean shouldShowCancellationExplanation,
- @NonNull String appPackageName) {
- mToken = token;
+ public CancelSelectionRequest(@NonNull RequestToken requestToken,
+ boolean shouldShowCancellationExplanation, @NonNull String packageName) {
+ mToken = requestToken.getToken();
mShouldShowCancellationExplanation = shouldShowCancellationExplanation;
- mAppPackageName = appPackageName;
+ mPackageName = packageName;
}
private CancelSelectionRequest(@NonNull Parcel in) {
mToken = in.readStrongBinder();
AnnotationValidations.validate(NonNull.class, null, mToken);
mShouldShowCancellationExplanation = in.readBoolean();
- mAppPackageName = in.readString8();
- AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+ mPackageName = in.readString8();
+ AnnotationValidations.validate(NonNull.class, null, mPackageName);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mToken);
dest.writeBoolean(mShouldShowCancellationExplanation);
- dest.writeString8(mAppPackageName);
+ dest.writeString8(mPackageName);
}
@Override
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index 7e6c781..f7fec23 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -36,5 +36,11 @@
public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
"android.credentials.selection.extra.REQ_FOR_ALL_OPTIONS";
+ /**
+ * The intent extra key for the final result receiver object
+ */
+ public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
+ "android.credentials.selection.extra.FINAL_RESPONSE_RECEIVER";
+
private Constants() {}
}
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 1837976..ac2bae4 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -210,7 +210,8 @@
.config_credentialManagerDialogComponent));
intent.setComponent(componentName);
intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
- new CancelSelectionRequest(requestToken, shouldShowCancellationUi, appPackageName));
+ new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
+ appPackageName));
return intent;
}
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 60bbae6..16d0802 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -106,7 +106,7 @@
private final String mType;
@NonNull
- private final String mAppPackageName;
+ private final String mPackageName;
private final boolean mHasPermissionToOverrideDefault;
@@ -172,8 +172,8 @@
/** Returns the display name of the app that made this request. */
@NonNull
- public String getAppPackageName() {
- return mAppPackageName;
+ public String getPackageName() {
+ return mPackageName;
}
/**
@@ -248,7 +248,7 @@
boolean isShowAllOptionsRequested) {
mToken = token;
mType = type;
- mAppPackageName = appPackageName;
+ mPackageName = appPackageName;
mCreateCredentialRequest = createCredentialRequest;
mGetCredentialRequest = getCredentialRequest;
mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
@@ -270,8 +270,8 @@
AnnotationValidations.validate(NonNull.class, null, mToken);
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
- mAppPackageName = appPackageName;
- AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+ mPackageName = appPackageName;
+ AnnotationValidations.validate(NonNull.class, null, mPackageName);
mCreateCredentialRequest = createCredentialRequest;
mGetCredentialRequest = getCredentialRequest;
mHasPermissionToOverrideDefault = in.readBoolean();
@@ -284,7 +284,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mToken);
dest.writeString8(mType);
- dest.writeString8(mAppPackageName);
+ dest.writeString8(mPackageName);
dest.writeTypedObject(mCreateCredentialRequest, flags);
dest.writeTypedObject(mGetCredentialRequest, flags);
dest.writeBoolean(mHasPermissionToOverrideDefault);
diff --git a/core/java/android/credentials/selection/RequestToken.java b/core/java/android/credentials/selection/RequestToken.java
index 27b83f8..f1953ce 100644
--- a/core/java/android/credentials/selection/RequestToken.java
+++ b/core/java/android/credentials/selection/RequestToken.java
@@ -30,6 +30,11 @@
* To compare if two requests pertain to the same session, compare their RequestTokens using
* the {@link RequestToken#equals(Object)} method.
*
+ * For example, when receiving a {@link android.credentials.selection.CancelSelectionRequest},
+ * the developer should use {@link RequestToken#getToken()} to retrieve the token from request and
+ * compare whether it is equal with the cached token using {@link RequestToken#equals(Object)}. Only
+ * cancel the request when two tokens are the same.
+ *
* @hide
*/
@SystemApi
@@ -39,6 +44,12 @@
@NonNull
private final IBinder mToken;
+ /** @hide **/
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
+ }
+
/** @hide */
@TestApi
@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 54e34ec..62473c5 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -76,6 +76,12 @@
*/
public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
+ /**
+ * Default value for {@link Settings.Secure#STYLUS_POINTER_ICON_ENABLED}.
+ * @hide
+ */
+ public static final int DEFAULT_STYLUS_POINTER_ICON_ENABLED = 1;
+
private InputSettings() {
}
@@ -383,14 +389,19 @@
}
/**
- * Whether a pointer icon will be shown over the location of a
- * stylus pointer.
+ * Whether a pointer icon will be shown over the location of a stylus pointer.
+ *
* @hide
*/
public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+ if (InputProperties.force_enable_stylus_pointer_icon().orElse(false)) {
+ return true;
+ }
return context.getResources()
- .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
- || InputProperties.force_enable_stylus_pointer_icon().orElse(false);
+ .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
+ && Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+ DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
}
/**
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 4c95e02..a968c6f 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -62,55 +62,55 @@
@SystemApi
public final class ProgramSelector implements Parcelable {
/** Invalid program type.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_INVALID = 0;
/** Analog AM radio (with or without RDS).
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_AM = 1;
/** analog FM radio (with or without RDS).
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_FM = 2;
/** AM HD Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_AM_HD = 3;
/** FM HD Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_FM_HD = 4;
/** Digital audio broadcasting.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_DAB = 5;
/** Digital Radio Mondiale.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_DRMO = 6;
/** SiriusXM Satellite Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_SXM = 7;
/** Vendor-specific, not synced across devices.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_VENDOR_START = 1000;
- /** @deprecated use {@link ProgramIdentifier} instead */
+ /** @deprecated use {@link Identifier} instead */
@Deprecated
public static final int PROGRAM_TYPE_VENDOR_END = 1999;
/**
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
* @removed mistakenly exposed previously
*/
@Deprecated
@@ -271,7 +271,7 @@
*/
public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
/**
- * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+ * @see #IDENTIFIER_TYPE_DAB_SID_EXT
*
* @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
*/
@@ -381,7 +381,7 @@
*/
public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
/**
- * @see {@link IDENTIFIER_TYPE_VENDOR_START}
+ * @see #IDENTIFIER_TYPE_VENDOR_START
*/
public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
/**
@@ -771,7 +771,7 @@
* Returns whether this Identifier's type is considered a category when filtering
* ProgramLists for category entries.
*
- * @see {@link ProgramList.Filter#areCategoriesIncluded()}
+ * @see ProgramList.Filter#areCategoriesIncluded
* @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
* vendor-specified type). True otherwise.
*/
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 41f21ef..61cf8901 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -311,7 +311,7 @@
}
/** Unique module identifier provided by the native service.
- * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+ * For use with {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
* @return the radio module unique identifier.
*/
public int getId() {
@@ -1561,7 +1561,7 @@
/** Main channel expressed in units according to band type.
* Currently all defined band types express channels as frequency in kHz
* @return the program channel
- * @deprecated Use {@link getSelector()} instead.
+ * @deprecated Use {@link ProgramInfo#getSelector} instead.
*/
@Deprecated
public int getChannel() {
@@ -1575,7 +1575,7 @@
/** Sub channel ID. E.g 1 for HD radio HD1
* @return the program sub channel
- * @deprecated Use {@link getSelector()} instead.
+ * @deprecated Use {@link ProgramInfo#getSelector} instead.
*/
@Deprecated
public int getSubChannel() {
@@ -1604,7 +1604,7 @@
/** {@code true} if the received program is digital (e.g HD radio)
* @return {@code true} if digital, {@code false} otherwise.
- * @deprecated Use {@link getLogicallyTunedTo()} instead.
+ * @deprecated Use {@link ProgramInfo#getLogicallyTunedTo()} instead.
*/
@Deprecated
public boolean isDigital() {
@@ -1913,7 +1913,8 @@
* Removes previously registered announcement listener.
*
* @param listener announcement listener, previously registered with
- * {@link addAnnouncementListener}
+ * {@link #addAnnouncementListener(Executor, Set, Announcement.OnListUpdatedListener)}
+ * or {@link #addAnnouncementListener(Set, Announcement.OnListUpdatedListener)}
*/
@RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index da6b9c2..67381ec 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -593,7 +593,7 @@
* Helper for getting the String key used by {@link RadioMetadata} from the
* corrsponding native integer key.
*
- * @param editorKey The key used by the editor
+ * @param nativeKey The key used by the editor
* @return the key used by this class or null if no mapping exists
* @hide
*/
@@ -743,11 +743,11 @@
* Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
* METADATA_KEYs defined in this class are used they may only be one of the following:
* <ul>
- * <li>{@link #MEADATA_KEY_CLOCK}</li>
+ * <li>{@link #METADATA_KEY_CLOCK}</li>
* </ul>
*
* @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
- * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
+ * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes.
* @return the same Builder instance.
*/
public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index b9bb059..f3efd89 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -238,6 +238,16 @@
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ /**
+ * Returns true if the policy is some type of adaptive charging policy.
+ * @hide
+ */
+ public static boolean isAdaptiveChargingPolicy(int policy) {
+ return policy == CHARGING_POLICY_ADAPTIVE_AC
+ || policy == CHARGING_POLICY_ADAPTIVE_AON
+ || policy == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ }
+
// values for "battery part status" property
/**
* Battery part status is not supported.
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 9bad0de..0ec8729 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+
/**
* Battery manager local system service interface.
*
@@ -84,6 +86,26 @@
*/
public abstract boolean getBatteryLevelLow();
+ public interface ChargingPolicyChangeListener {
+ void onChargingPolicyChanged(int newPolicy);
+ }
+
+ /**
+ * Register a listener for changes to {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * The charging policy can't be added to the BATTERY_CHANGED intent because it requires
+ * the BATTERY_STATS permission.
+ */
+ public abstract void registerChargingPolicyChangeListener(
+ @NonNull ChargingPolicyChangeListener chargingPolicyChangeListener);
+
+ /**
+ * Returns the value of {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * This will return {@link Integer#MIN_VALUE} if the device does not support the property.
+ *
+ * @see BatteryManager#getIntProperty(int)
+ */
+ public abstract int getChargingPolicy();
+
/**
* Returns a non-zero value if an unsupported charger is attached.
*
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 08b32bf..c9c91fc 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -33,7 +33,6 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.util.Log;
import android.util.MathUtils;
import com.android.internal.util.Preconditions;
@@ -54,7 +53,6 @@
* <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
*/
public abstract class VibrationEffect implements Parcelable {
- private static final String TAG = "VibrationEffect";
// Stevens' coefficient to scale the perceived vibration intensity.
private static final float SCALE_GAMMA = 0.65f;
// If a vibration is playing for longer than 1s, it's probably not haptic feedback
@@ -397,32 +395,26 @@
return null;
}
- try {
- final ContentResolver cr = context.getContentResolver();
- Uri uncanonicalUri = cr.uncanonicalize(uri);
- if (uncanonicalUri == null) {
- // If we already had an uncanonical URI, it's possible we'll get null back here. In
- // this case, just use the URI as passed in since it wasn't canonicalized in the
- // first place.
- uncanonicalUri = uri;
- }
+ final ContentResolver cr = context.getContentResolver();
+ Uri uncanonicalUri = cr.uncanonicalize(uri);
+ if (uncanonicalUri == null) {
+ // If we already had an uncanonical URI, it's possible we'll get null back here. In
+ // this case, just use the URI as passed in since it wasn't canonicalized in the first
+ // place.
+ uncanonicalUri = uri;
+ }
- for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
- if (uris[i] == null) {
- continue;
- }
- Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
- if (mappedUri == null) {
- continue;
- }
- if (mappedUri.equals(uncanonicalUri)) {
- return get(RINGTONES[i]);
- }
+ for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
+ if (uris[i] == null) {
+ continue;
}
- } catch (Exception e) {
- // Don't give unexpected exceptions to callers if the Uri's ContentProvider is
- // misbehaving - it's very unlikely to be mapped in that case anyway.
- Log.e(TAG, "Exception getting default vibration for Uri " + uri, e);
+ Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
+ if (mappedUri == null) {
+ continue;
+ }
+ if (mappedUri.equals(uncanonicalUri)) {
+ return get(RINGTONES[i]);
+ }
}
return null;
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 6c728a4..abfa4e3 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -122,3 +122,11 @@
is_fixed_read_only: true
bug: "309792384"
}
+
+flag {
+ namespace: "system_performance"
+ name: "telemetry_apis_framework_initialization"
+ description: "Control framework initialization APIs of telemetry APIs feature."
+ is_fixed_read_only: true
+ bug: "324241334"
+}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 437668c..ea9375e 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -16,13 +16,6 @@
flag {
namespace: "haptics"
- name: "haptics_customization_ringtone_v2_enabled"
- description: "Enables the usage of the new RingtoneV2 class"
- bug: "241918098"
-}
-
-flag {
- namespace: "haptics"
name: "enable_vibration_serialization_apis"
description: "Enables the APIs for vibration serialization/deserialization."
bug: "245129509"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef2d5eb..ce7a026 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12312,6 +12312,16 @@
public static final String PRIVATE_SPACE_AUTO_LOCK = "private_space_auto_lock";
/**
+ * Toggle for enabling stylus pointer icon. Pointer icons for styluses will only be be shown
+ * when this is enabled. Enabling this alone won't enable the stylus pointer;
+ * config_enableStylusPointerIcon needs to be true as well.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2841dc0..658cec8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4943,6 +4943,13 @@
public static final String COLUMN_IS_NTN = "is_ntn";
/**
+ * TelephonyProvider column name for transferred status
+ *
+ * @hide
+ */
+ public static final String COLUMN_TRANSFER_STATUS = "transfer_status";
+
+ /**
* TelephonyProvider column name to indicate the service capability bitmasks.
*
* @hide
@@ -5021,7 +5028,8 @@
COLUMN_SATELLITE_ENABLED,
COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
COLUMN_IS_NTN,
- COLUMN_SERVICE_CAPABILITIES
+ COLUMN_SERVICE_CAPABILITIES,
+ COLUMN_TRANSFER_STATUS
);
/**
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 1afe8d9..da8817a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -26,8 +26,8 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.ClipData;
+import android.content.Intent;
import android.content.IntentSender;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -190,7 +190,7 @@
@Nullable private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
- @Nullable private final Bundle mAuthenticationExtras;
+ @Nullable private Intent mCredentialFillInIntent;
@Nullable String mId;
@@ -229,7 +229,7 @@
mInlinePresentation = inlinePresentation;
mInlineTooltipPresentation = inlineTooltipPresentation;
mAuthentication = authentication;
- mAuthenticationExtras = null;
+ mCredentialFillInIntent = null;
mId = id;
}
@@ -252,7 +252,7 @@
mInlinePresentation = dataset.mInlinePresentation;
mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
mAuthentication = dataset.mAuthentication;
- mAuthenticationExtras = dataset.mAuthenticationExtras;
+ mCredentialFillInIntent = dataset.mCredentialFillInIntent;
mId = dataset.mId;
mAutofillDatatypes = dataset.mAutofillDatatypes;
}
@@ -271,7 +271,7 @@
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
- mAuthenticationExtras = builder.mAuthenticationExtras;
+ mCredentialFillInIntent = builder.mCredentialFillInIntent;
mId = builder.mId;
mAutofillDatatypes = builder.mAutofillDatatypes;
}
@@ -354,8 +354,14 @@
/** @hide */
@Hide
- public @Nullable Bundle getAuthenticationExtras() {
- return mAuthenticationExtras;
+ public @Nullable Intent getCredentialFillInIntent() {
+ return mCredentialFillInIntent;
+ }
+
+ /** @hide */
+ @Hide
+ public void setCredentialFillInIntent(Intent intent) {
+ mCredentialFillInIntent = intent;
}
/** @hide */
@@ -415,7 +421,7 @@
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
- if (mAuthenticationExtras != null) {
+ if (mCredentialFillInIntent != null) {
builder.append(", hasAuthenticationExtras");
}
if (mAutofillDatatypes != null) {
@@ -472,7 +478,7 @@
@Nullable private InlinePresentation mInlineTooltipPresentation;
private IntentSender mAuthentication;
- private Bundle mAuthenticationExtras;
+ private Intent mCredentialFillInIntent;
private boolean mDestroyed;
@Nullable private String mId;
@@ -655,9 +661,9 @@
* @hide
*/
@Hide
- public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) {
+ public @NonNull Builder setCredentialFillInIntent(@Nullable Intent credentialFillInIntent) {
throwIfDestroyed();
- mAuthenticationExtras = authenticationExtra;
+ mCredentialFillInIntent = credentialFillInIntent;
return this;
}
@@ -1401,7 +1407,7 @@
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
parcel.writeInt(mEligibleReason);
- parcel.writeTypedObject(mAuthenticationExtras, flags);
+ parcel.writeTypedObject(mCredentialFillInIntent, flags);
}
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1437,7 +1443,7 @@
android.content.IntentSender.class);
final String datasetId = parcel.readString();
final int eligibleReason = parcel.readInt();
- final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
+ final Intent credentialFillInIntent = parcel.readTypedObject(Intent.CREATOR);
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
@@ -1482,7 +1488,7 @@
fieldDialogPresentation);
}
builder.setAuthentication(authentication);
- builder.setAuthenticationExtras(authenticationExtras);
+ builder.setCredentialFillInIntent(credentialFillInIntent);
builder.setId(datasetId);
Dataset dataset = builder.build();
dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 09ec933..c43ba6c 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -56,6 +56,7 @@
* <p>See the main {@link AutofillService} documentation for more details and examples.
*/
public final class FillResponse implements Parcelable {
+ // common_typos_disable
/**
* Flag used to generate {@link FillEventHistory.Event events} of type
@@ -82,11 +83,17 @@
*/
public static final int FLAG_DELAY_FILL = 0x4;
+ /**
+ * @hide
+ */
+ public static final int FLAG_CREDENTIAL_MANAGER_RESPONSE = 0x8;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_TRACK_CONTEXT_COMMITED,
FLAG_DISABLE_ACTIVITY_ONLY,
- FLAG_DELAY_FILL
+ FLAG_DELAY_FILL,
+ FLAG_CREDENTIAL_MANAGER_RESPONSE
})
@Retention(RetentionPolicy.SOURCE)
@interface FillResponseFlags {}
@@ -834,7 +841,9 @@
public Builder setFlags(@FillResponseFlags int flags) {
throwIfDestroyed();
mFlags = Preconditions.checkFlagsArgument(flags,
- FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
+ FLAG_TRACK_CONTEXT_COMMITED
+ | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL
+ | FLAG_CREDENTIAL_MANAGER_RESPONSE);
return this;
}
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index ec44100..763c79e 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -4,4 +4,4 @@
# The owner here should not be assist owner
liangyuchen@google.com
-tuanng@google.com
+adudani@google.com
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 44a13c4..0556188 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,8 +28,11 @@
* @hide
*/
oneway interface IWearableSensingService {
+ void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+ void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
+ void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
void startDetection(in AmbientContextEventRequest request, in String packageName,
in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
void stopDetection(in String packageName);
diff --git a/core/java/android/service/wearable/WearableSensingDataRequester.java b/core/java/android/service/wearable/WearableSensingDataRequester.java
new file mode 100644
index 0000000..5a8104f
--- /dev/null
+++ b/core/java/android/service/wearable/WearableSensingDataRequester.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.wearable;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.wearable.Flags;
+import android.app.wearable.WearableSensingDataRequest;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
+/**
+ * An interface to request wearable sensing data.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+@SystemApi
+public interface WearableSensingDataRequester {
+
+ /** An unknown status. */
+ int STATUS_UNKNOWN = 0;
+
+ /** The value of the status code that indicates success. */
+ int STATUS_SUCCESS = 1;
+
+ /**
+ * The value of the status code that indicates the request is rejected because the data request
+ * observer PendingIntent has been cancelled.
+ */
+ int STATUS_OBSERVER_CANCELLED = 2;
+
+ /**
+ * The value of the status code that indicates the request is rejected because it is larger than
+ * {@link WearableSensingDataRequest#getMaxRequestSize()}.
+ */
+ int STATUS_TOO_LARGE = 3;
+
+ /**
+ * The value of the status code that indicates the request is rejected because it exceeds the
+ * rate limit. See {@link WearableSensingDataRequest#getRateLimit()}.
+ */
+ int STATUS_TOO_FREQUENT = 4;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"STATUS_"},
+ value = {
+ STATUS_UNKNOWN,
+ STATUS_SUCCESS,
+ STATUS_OBSERVER_CANCELLED,
+ STATUS_TOO_LARGE,
+ STATUS_TOO_FREQUENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface StatusCode {}
+
+ /**
+ * Sends a data request. See {@link WearableSensingService#onDataRequestObserverRegistered(int,
+ * String, WearableSensingDataRequester, Consumer)} for size and rate restrictions on data
+ * requests.
+ *
+ * @param dataRequest The data request to send.
+ * @param statusConsumer A consumer that handles the status code for the data request.
+ */
+ void requestData(
+ @NonNull WearableSensingDataRequest dataRequest,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer);
+}
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index e7e44a5..d25cff7 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -17,12 +17,15 @@
package android.service.wearable;
import android.annotation.BinderThread;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.wearable.Flags;
+import android.app.wearable.WearableSensingDataRequest;
import android.app.wearable.WearableSensingManager;
import android.content.Intent;
import android.os.Bundle;
@@ -34,11 +37,13 @@
import android.service.ambientcontext.AmbientContextDetectionResult;
import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
import android.util.Slog;
+import android.util.SparseArray;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -87,6 +92,9 @@
public static final String SERVICE_INTERFACE =
"android.service.wearable.WearableSensingService";
+ private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap =
+ new SparseArray<>();
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -94,17 +102,20 @@
return new IWearableSensingService.Stub() {
/** {@inheritDoc} */
@Override
+ public void provideSecureWearableConnection(
+ ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ Objects.requireNonNull(secureWearableConnection);
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
+ WearableSensingService.this.onSecureWearableConnectionProvided(
+ secureWearableConnection, consumer);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor,
- RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
Objects.requireNonNull(parcelFileDescriptor);
- Consumer<Integer> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putInt(
- STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataStreamProvided(
parcelFileDescriptor, consumer);
}
@@ -116,38 +127,87 @@
SharedMemory sharedMemory,
RemoteCallback callback) {
Objects.requireNonNull(data);
- Consumer<Integer> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putInt(
- STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
}
/** {@inheritDoc} */
@Override
- public void startDetection(@NonNull AmbientContextEventRequest request,
- String packageName, RemoteCallback detectionResultCallback,
+ public void registerDataRequestObserver(
+ int dataType,
+ RemoteCallback dataRequestCallback,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ Objects.requireNonNull(dataRequestCallback);
+ Objects.requireNonNull(statusCallback);
+ WearableSensingDataRequester dataRequester;
+ synchronized (mDataRequestObserverIdToRequesterMap) {
+ dataRequester =
+ mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+ if (dataRequester == null) {
+ dataRequester = createDataRequester(dataRequestCallback);
+ mDataRequestObserverIdToRequesterMap.put(
+ dataRequestObserverId, dataRequester);
+ }
+ }
+ Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+ WearableSensingService.this.onDataRequestObserverRegistered(
+ dataType, packageName, dataRequester, statusConsumer);
+ }
+
+ @Override
+ public void unregisterDataRequestObserver(
+ int dataType,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ WearableSensingDataRequester dataRequester;
+ synchronized (mDataRequestObserverIdToRequesterMap) {
+ dataRequester =
+ mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+ if (dataRequester == null) {
+ Slog.w(
+ TAG,
+ "dataRequestObserverId not found, cannot unregister data"
+ + " request observer.");
+ return;
+ }
+ mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId);
+ }
+ Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+ WearableSensingService.this.onDataRequestObserverUnregistered(
+ dataType, packageName, dataRequester, statusConsumer);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void startDetection(
+ @NonNull AmbientContextEventRequest request,
+ String packageName,
+ RemoteCallback detectionResultCallback,
RemoteCallback statusCallback) {
Objects.requireNonNull(request);
Objects.requireNonNull(packageName);
Objects.requireNonNull(detectionResultCallback);
Objects.requireNonNull(statusCallback);
- Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result);
- detectionResultCallback.sendResult(bundle);
- };
- Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
- status);
- statusCallback.sendResult(bundle);
- };
+ Consumer<AmbientContextDetectionResult> detectionResultConsumer =
+ result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
+ result);
+ detectionResultCallback.sendResult(bundle);
+ };
+ Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
+ status -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionServiceStatus
+ .STATUS_RESPONSE_BUNDLE_KEY,
+ status);
+ statusCallback.sendResult(bundle);
+ };
WearableSensingService.this.onStartDetection(
request, packageName, statusConsumer, detectionResultConsumer);
Slog.d(TAG, "startDetection " + request);
@@ -162,23 +222,26 @@
/** {@inheritDoc} */
@Override
- public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes,
- String packageName, RemoteCallback callback) {
+ public void queryServiceStatus(
+ @AmbientContextEvent.EventCode int[] eventTypes,
+ String packageName,
+ RemoteCallback callback) {
Objects.requireNonNull(eventTypes);
Objects.requireNonNull(packageName);
Objects.requireNonNull(callback);
- Consumer<AmbientContextDetectionServiceStatus> consumer = response -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(
- AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
- response);
- callback.sendResult(bundle);
- };
+ Consumer<AmbientContextDetectionServiceStatus> consumer =
+ response -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ AmbientContextDetectionServiceStatus
+ .STATUS_RESPONSE_BUNDLE_KEY,
+ response);
+ callback.sendResult(bundle);
+ };
Integer[] events = intArrayToIntegerArray(eventTypes);
WearableSensingService.this.onQueryServiceStatus(
new HashSet<>(Arrays.asList(events)), packageName, consumer);
}
-
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
@@ -186,6 +249,30 @@
}
/**
+ * Called when a secure connection to the wearable is available. See {@link
+ * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)}
+ * for details about the secure connection.
+ *
+ * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link
+ * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by
+ * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor,
+ * Executor, Consumer)}.
+ *
+ * <p>The implementing class should override this method. It should return an appropriate status
+ * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}.
+ *
+ * @param secureWearableConnection The secure connection to the wearable.
+ * @param statusConsumer The consumer for the service status.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+ @BinderThread
+ public void onSecureWearableConnectionProvided(
+ @NonNull ParcelFileDescriptor secureWearableConnection,
+ @NonNull Consumer<Integer> statusConsumer) {
+ statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+ }
+
+ /**
* Called when a data stream to the wearable is provided. This data stream can be used to obtain
* data from a wearable device. It is up to the implementation to maintain the data stream and
* close the data stream when it is finished.
@@ -198,19 +285,19 @@
@NonNull Consumer<Integer> statusConsumer);
/**
- * Called when configurations and read-only data in a {@link PersistableBundle}
- * can be used by the WearableSensingService and sends the result to the {@link Consumer}
- * right after the call. It is dependent on the application to define the type of data to
- * provide. This is used by applications that will also provide an implementation of an isolated
- * WearableSensingService. If the data was provided successfully
- * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+ * Called when configurations and read-only data in a {@link PersistableBundle} can be used by
+ * the WearableSensingService and sends the result to the {@link Consumer} right after the call.
+ * It is dependent on the application to define the type of data to provide. This is used by
+ * applications that will also provide an implementation of an isolated WearableSensingService.
+ * If the data was provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be
+ * provided.
*
* @param data Application configuration data to provide to the {@link WearableSensingService}.
- * PersistableBundle does not allow any remotable objects or other contents
- * that can be used to communicate with other processes.
- * @param sharedMemory The unrestricted data blob to
- * provide to the {@link WearableSensingService}. Use this to provide the
- * sensing models data or other such data to the trusted process.
+ * PersistableBundle does not allow any remotable objects or other contents that can be used
+ * to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to provide to the {@link
+ * WearableSensingService}. Use this to provide the sensing models data or other such data
+ * to the trusted process.
* @param statusConsumer the consumer for the service status.
*/
@BinderThread
@@ -220,6 +307,68 @@
@NonNull Consumer<Integer> statusConsumer);
/**
+ * Called when a data request observer is registered. Each request must not be larger than
+ * {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link
+ * WearableSensingDataRequester#getRateLimit()} requests can be sent every rolling {@link
+ * WearableSensingDataRequester#getRateLimitWindowSize()}. Requests that are too large or too
+ * frequent will be dropped by the system. See {@link
+ * WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details
+ * about the status code returned for each request.
+ *
+ * <p>The implementing class should override this method. After the data requester is received,
+ * it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code
+ * statusConsumer} unless it encounters an error condition described by a status code listed in
+ * {@link WearableSensingManager}, such as {@link
+ * WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the
+ * corresponding status code.
+ *
+ * @param dataType The data type the observer is registered for. Values are defined by the
+ * application that implements this class.
+ * @param packageName The package name of the app that will receive the requests.
+ * @param dataRequester A handle to the observer registered. It can be used to request data of
+ * the specified data type.
+ * @param statusConsumer the consumer for the status of the data request observer registration.
+ * This is different from the status for each data request.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ @BinderThread
+ public void onDataRequestObserverRegistered(
+ int dataType,
+ @NonNull String packageName,
+ @NonNull WearableSensingDataRequester dataRequester,
+ @NonNull Consumer<Integer> statusConsumer) {
+ statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+ }
+
+ /**
+ * Called when a data request observer is unregistered.
+ *
+ * <p>The implementing class should override this method. It should send a {@link
+ * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
+ * encounters an error condition described by a status code listed in {@link
+ * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
+ * in which case it should return the corresponding status code.
+ *
+ * @param dataType The data type the observer is for.
+ * @param packageName The package name of the app that will receive the requests sent to the
+ * dataRequester.
+ * @param dataRequester A handle to the observer to be unregistered. It is the exact same
+ * instance provided in a previous {@link #onDataRequestConsumerRegistered(int, String,
+ * WearableSensingDataRequester, Consumer)} invocation.
+ * @param statusConsumer the consumer for the status of the data request observer
+ * unregistration. This is different from the status for each data request.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+ @BinderThread
+ public void onDataRequestObserverUnregistered(
+ int dataType,
+ @NonNull String packageName,
+ @NonNull WearableSensingDataRequester dataRequester,
+ @NonNull Consumer<Integer> statusConsumer) {
+ statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+ }
+
+ /**
* Called when a client app requests starting detection of the events in the request. The
* implementation should keep track of whether the user has explicitly consented to detecting
* the events using on-going ambient sensor (e.g. microphone), and agreed to share the
@@ -275,4 +424,32 @@
}
return intArray;
}
+
+ private static WearableSensingDataRequester createDataRequester(
+ RemoteCallback dataRequestCallback) {
+ return (request, requestStatusConsumer) -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request);
+ RemoteCallback requestStatusCallback =
+ new RemoteCallback(
+ requestStatusBundle -> {
+ requestStatusConsumer.accept(
+ requestStatusBundle.getInt(
+ WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY));
+ });
+ bundle.putParcelable(
+ WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+ requestStatusCallback);
+ dataRequestCallback.sendResult(bundle);
+ };
+ }
+
+ @NonNull
+ private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
+ return response -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
+ statusCallback.sendResult(bundle);
+ };
+ }
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1ea80f1..8ddb42d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -172,7 +172,7 @@
/**
* Value for justification mode indicating the text is justified by stretching letter spacing.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
@@ -1831,7 +1831,7 @@
* @return the number of cluster count in the line.
*/
@IntRange(from = 0)
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
boolean includeTrailingWhitespace) {
final int start = getLineStart(line);
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 224e5d8..bde9c77 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -305,7 +305,7 @@
}
mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
mAddedLetterSpacingInPx = 0;
- } else { // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+ } else { // justificationMode == Layout.JUSTIFICATION_MODE_LETTER_SPACING
LineInfo lineInfo = new LineInfo();
float width = Math.abs(measure(end, false, null, null, lineInfo));
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a49aee1..f37c4c2a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -77,7 +77,7 @@
}
flag {
- name: "inter_character_justification"
+ name: "letter_spacing_justification"
namespace: "text"
description: "A feature flag that implement inter character justification."
bug: "283193133"
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7bae7ec..4ba4ee3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1497,13 +1497,21 @@
* {@link View#SYSTEM_UI_LAYOUT_FLAGS} as well the
* {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag and fits content according
* to these flags.
- * </p>
+ *
* <p>
* If set to {@code false}, the framework will not fit the content view to the insets and will
* just pass through the {@link WindowInsets} to the content view.
- * </p>
+ *
+ * <p>
+ * If the app targets
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ * the behavior will be like setting this to {@code false}, and cannot be changed.
+ *
* @param decorFitsSystemWindows Whether the decor view should fit root-level content views for
* insets.
+ * @deprecated Make space in the container views to prevent the critical elements from getting
+ * obscured by {@link WindowInsets.Type#systemBars()} or
+ * {@link WindowInsets.Type#displayCutout()} instead.
*/
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
}
@@ -2597,7 +2605,9 @@
/**
* @return the color of the status bar.
+ * @deprecated This is no longer needed since the setter is deprecated.
*/
+ @Deprecated
@ColorInt
public abstract int getStatusBarColor();
@@ -2614,13 +2624,29 @@
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
* <p>
* The transitionName for the view background will be "android:status:background".
- * </p>
+ *
+ * <p>
+ * If the color is transparent and the window enforces the status bar contrast, the system
+ * will determine whether a scrim is necessary and draw one on behalf of the app to ensure
+ * that the status bar has enough contrast with the contents of this app, and set an appropriate
+ * effective bar background accordingly.
+ *
+ * <p>
+ * If the app targets
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ * the color will be transparent and cannot be changed.
+ *
+ * @see #setNavigationBarContrastEnforced
+ * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
*/
+ @Deprecated
public abstract void setStatusBarColor(@ColorInt int color);
/**
* @return the color of the navigation bar.
+ * @deprecated This is no longer needed since the setter is deprecated.
*/
+ @Deprecated
@ColorInt
public abstract int getNavigationBarColor();
@@ -2637,9 +2663,24 @@
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
* <p>
* The transitionName for the view background will be "android:navigation:background".
- * </p>
+ *
+ * <p>
+ * If the color is transparent and the window enforces the navigation bar contrast, the system
+ * will determine whether a scrim is necessary and draw one on behalf of the app to ensure that
+ * the navigation bar has enough contrast with the contents of this app, and set an appropriate
+ * effective bar background accordingly.
+ *
+ * <p>
+ * If the app targets
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ * the color will be transparent and cannot be changed.
+ *
* @attr ref android.R.styleable#Window_navigationBarColor
+ * @see #setNavigationBarContrastEnforced
+ * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+ * {@link WindowInsets.Type#tappableElement()} instead.
*/
+ @Deprecated
public abstract void setNavigationBarColor(@ColorInt int color);
/**
@@ -2651,9 +2692,17 @@
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
*
+ * <p>
+ * If the app targets
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ * the color will be transparent and cannot be changed.
+ *
* @param dividerColor The color of the thin line.
* @attr ref android.R.styleable#Window_navigationBarDividerColor
+ * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+ * {@link WindowInsets.Type#tappableElement()} instead.
*/
+ @Deprecated
public void setNavigationBarDividerColor(@ColorInt int dividerColor) {
}
@@ -2663,7 +2712,9 @@
* @return The color of the navigation bar divider color.
* @see #setNavigationBarColor(int)
* @attr ref android.R.styleable#Window_navigationBarDividerColor
+ * @deprecated This is no longer needed since the setter is deprecated.
*/
+ @Deprecated
public @ColorInt int getNavigationBarDividerColor() {
return 0;
}
@@ -2682,7 +2733,9 @@
* @see android.R.attr#enforceStatusBarContrast
* @see #isStatusBarContrastEnforced
* @see #setStatusBarColor
+ * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
*/
+ @Deprecated
public void setStatusBarContrastEnforced(boolean ensureContrast) {
}
@@ -2697,7 +2750,9 @@
* @see android.R.attr#enforceStatusBarContrast
* @see #setStatusBarContrastEnforced
* @see #setStatusBarColor
+ * @deprecated This is not needed since the setter is deprecated.
*/
+ @Deprecated
public boolean isStatusBarContrastEnforced() {
return false;
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c49fce5..848261d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -179,9 +179,29 @@
}
}
+ /**
+ * Sets {@link com.android.server.wm.WindowManagerService} for the system process.
+ * <p>
+ * It is needed to prevent possible deadlock. A possible scenario is:
+ * In system process, WMS holds {@link com.android.server.wm.WindowManagerGlobalLock} to call
+ * {@code WindowManagerGlobal} APIs and wait to lock {@code WindowManagerGlobal} itself
+ * (i.e. call {@link #getWindowManagerService()} in the global lock), while
+ * another component may lock {@code WindowManagerGlobal} and wait to lock
+ * {@link com.android.server.wm.WindowManagerGlobalLock}(i.e call {@link #addView} in the
+ * system process, which calls to {@link com.android.server.wm.WindowManagerService} API
+ * directly).
+ */
+ public static void setWindowManagerServiceForSystemProcess(@NonNull IWindowManager wms) {
+ sWindowManagerService = wms;
+ }
+
@Nullable
@UnsupportedAppUsage
public static IWindowManager getWindowManagerService() {
+ if (sWindowManagerService != null) {
+ // Use WMS directly without locking WMGlobal to prevent deadlock.
+ return sWindowManagerService;
+ }
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 559ccfea7..7ebabee 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -64,6 +64,7 @@
import android.service.autofill.FillEventHistory;
import android.service.autofill.Flags;
import android.service.autofill.UserData;
+import android.service.credentials.CredentialProviderService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -2382,7 +2383,18 @@
return;
}
- final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+ final Parcelable result;
+ if (data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT) != null) {
+ result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+ } else if (data.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE) != null
+ && Flags.autofillCredmanIntegration()) {
+ result = data.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE);
+ } else {
+ result = null;
+ }
+
final Bundle responseData = new Bundle();
responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1dd99ba..6a4408b 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,13 +1,6 @@
package: "android.view.flags"
flag {
- name: "enable_surface_native_alloc_registration"
- namespace: "toolkit"
- description: "Feature flag for registering surfaces with the VM for faster cleanup"
- bug: "306193257"
-}
-
-flag {
name: "enable_surface_native_alloc_registration_ro"
namespace: "toolkit"
description: "Feature flag for registering surfaces with the VM for faster"
diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig
new file mode 100644
index 0000000..201b7ad
--- /dev/null
+++ b/core/java/android/view/flags/window_insets.aconfig
@@ -0,0 +1,9 @@
+package: "android.view.flags"
+
+flag {
+ name: "customizable_window_headers"
+ namespace: "lse_desktop_experience"
+ description: "Flag to control the caption bar appearance and to fit app content in its empty space"
+ bug: "316387515"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
index b0f3578..a051c1b 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
@@ -89,4 +89,7 @@
*/
@Nullable
String getRequiredDisplayCategory();
+
+ /** Gets the permissions necessary for launching the activity when using content URIs. */
+ int getRequireContentUriPermissionFromCaller();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
index 2f977ee..1218793 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
@@ -36,9 +36,9 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.internal.pm.pkg.parsing.ParsingUtils;
import java.util.Collections;
import java.util.Locale;
@@ -97,6 +97,8 @@
@Nullable
private String mRequiredDisplayCategory;
+ private int mRequireContentUriPermissionFromCaller;
+
public ParsedActivityImpl(ParsedActivityImpl other) {
super(other);
this.theme = other.theme;
@@ -124,6 +126,7 @@
this.windowLayout = other.windowLayout;
this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
this.mRequiredDisplayCategory = other.mRequiredDisplayCategory;
+ this.mRequireContentUriPermissionFromCaller = other.mRequireContentUriPermissionFromCaller;
}
/**
@@ -192,6 +195,8 @@
alias.setDirectBootAware(target.isDirectBootAware());
alias.setProcessName(target.getProcessName());
alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory());
+ alias.setRequireContentUriPermissionFromCaller(
+ target.getRequireContentUriPermissionFromCaller());
return alias;
// Not all attributes from the target ParsedActivity are copied to the alias.
@@ -320,6 +325,7 @@
}
sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
dest.writeString8(this.mRequiredDisplayCategory);
+ dest.writeInt(this.mRequireContentUriPermissionFromCaller);
}
public ParsedActivityImpl() {
@@ -355,6 +361,7 @@
}
this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
this.mRequiredDisplayCategory = in.readString8();
+ this.mRequireContentUriPermissionFromCaller = in.readInt();
}
@NonNull
@@ -412,7 +419,8 @@
int rotationAnimation,
int colorMode,
@Nullable ActivityInfo.WindowLayout windowLayout,
- @Nullable String requiredDisplayCategory) {
+ @Nullable String requiredDisplayCategory,
+ int requireContentUriPermissionFromCaller) {
this.theme = theme;
this.uiOptions = uiOptions;
this.targetActivity = targetActivity;
@@ -438,6 +446,7 @@
this.colorMode = colorMode;
this.windowLayout = windowLayout;
this.mRequiredDisplayCategory = requiredDisplayCategory;
+ this.mRequireContentUriPermissionFromCaller = requireContentUriPermissionFromCaller;
// onConstructed(); // You can define this method to get a callback
}
@@ -563,6 +572,11 @@
}
@DataClass.Generated.Member
+ public int getRequireContentUriPermissionFromCaller() {
+ return mRequireContentUriPermissionFromCaller;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedActivityImpl setTheme( int value) {
theme = value;
return this;
@@ -694,11 +708,17 @@
return this;
}
+ @DataClass.Generated.Member
+ public @NonNull ParsedActivityImpl setRequireContentUriPermissionFromCaller( int value) {
+ mRequireContentUriPermissionFromCaller = value;
+ return this;
+ }
+
@DataClass.Generated(
- time = 1701338377709L,
+ time = 1706180262165L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java",
- inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\nprivate int mRequireContentUriPermissionFromCaller\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index c3f7dab..9f71d88 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -241,6 +241,10 @@
activity.setRequiredDisplayCategory(requiredDisplayCategory);
+ activity.setRequireContentUriPermissionFromCaller(sa.getInt(
+ R.styleable.AndroidManifestActivity_requireContentUriPermissionFromCaller,
+ ActivityInfo.CONTENT_URI_PERMISSION_NONE));
+
return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
false /*isAlias*/, visibleToEphemeral, input,
R.styleable.AndroidManifestActivity_parentActivityName,
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b4f9ee3..5239245 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -120,7 +120,6 @@
import android.window.ProxyOnBackInvokedDispatcher;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.view.menu.ContextMenuBuilder;
import com.android.internal.view.menu.IconMenuPresenter;
import com.android.internal.view.menu.ListMenuPresenter;
@@ -369,8 +368,7 @@
boolean mDecorFitsSystemWindows = true;
- @VisibleForTesting
- public final boolean mEdgeToEdgeEnforced;
+ private boolean mEdgeToEdgeEnforced;
private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
@@ -390,11 +388,6 @@
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
- mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(context.getApplicationInfo(), true /* local */);
- if (mEdgeToEdgeEnforced) {
- getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
- mDecorFitsSystemWindows = false;
- }
}
/**
@@ -436,15 +429,18 @@
*
* @param info The application to query.
* @param local Whether this is called from the process of the given application.
+ * @param windowStyle The style of the window.
* @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
*/
- public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local) {
- return info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
- || (Flags.enforceEdgeToEdge() && (local
- // Calling this doesn't require a permission.
- ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
- // Calling this requires permissions.
- : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)));
+ public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local,
+ TypedArray windowStyle) {
+ return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)
+ && (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+ || (Flags.enforceEdgeToEdge() && (local
+ // Calling this doesn't require a permission.
+ ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+ // Calling this requires permissions.
+ : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE))));
}
@Override
@@ -2470,6 +2466,13 @@
System.out.println(s);
}
+ mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(
+ getContext().getApplicationInfo(), true /* local */, a);
+ if (mEdgeToEdgeEnforced) {
+ getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
+ mDecorFitsSystemWindows = false;
+ }
+
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index a8d0d37..889434f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -168,12 +168,12 @@
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr) {
+ @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -432,8 +432,14 @@
final List<MessagingMessage> newHistoricMessagingMessages =
createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText);
+ // Add our new MessagingMessages to groups
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<Person> senders = new ArrayList<>();
+ // Lets first find the groups (populate `groups` and `senders`)
+ findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders);
+
return new MessagingData(user, showSpinner, unreadCount,
- newHistoricMessagingMessages, newMessagingMessages);
+ newHistoricMessagingMessages, newMessagingMessages, groups, senders);
}
/**
@@ -509,21 +515,13 @@
setUser(messagingData.getUser());
setUnreadCount(messagingData.getUnreadCount());
- List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
- List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
// Copy our groups, before they get clobbered
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
- // Add our new MessagingMessages to groups
- List<List<MessagingMessage>> groups = new ArrayList<>();
- List<Person> senders = new ArrayList<>();
-
- // Lets first find the groups (populate `groups` and `senders`)
- findGroups(historicMessages, messages, groups, senders);
-
// Let's now create the views and reorder them accordingly
// side-effect: updates mGroups, mAddedGroups
- createGroupViews(groups, senders, messagingData.getShowSpinner());
+ createGroupViews(messagingData.getGroups(), messagingData.getSenders(),
+ messagingData.getShowSpinner());
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
@@ -536,8 +534,8 @@
historicMessage.removeMessage(mToRecycle);
}
- mMessages = messages;
- mHistoricMessages = historicMessages;
+ mMessages = messagingData.getNewMessagingMessages();
+ mHistoricMessages = messagingData.getHistoricMessagingMessages();
updateHistoricMessageVisibility();
updateTitleAndNamesDisplay();
@@ -935,7 +933,7 @@
}
private void createGroupViews(List<List<MessagingMessage>> groups,
- List<Person> senders, boolean showSpinner) {
+ List<Person> senders, boolean showSpinner) {
mGroups.clear();
for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
List<MessagingMessage> group = groups.get(groupIndex);
@@ -983,9 +981,12 @@
}
}
+ /**
+ * Finds groups and senders from the given messaging messages and fills outGroups and outSenders
+ */
private void findGroups(List<MessagingMessage> historicMessages,
- List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
- List<Person> senders) {
+ List<MessagingMessage> messages, Person user, List<List<MessagingMessage>> outGroups,
+ List<Person> outSenders) {
CharSequence currentSenderKey = null;
List<MessagingMessage> currentGroup = null;
int histSize = historicMessages.size();
@@ -1003,14 +1004,14 @@
isNewGroup |= !TextUtils.equals(key, currentSenderKey);
if (isNewGroup) {
currentGroup = new ArrayList<>();
- groups.add(currentGroup);
+ outGroups.add(currentGroup);
if (sender == null) {
- sender = mUser;
+ sender = user;
} else {
// Remove all formatting from the sender name
sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build();
}
- senders.add(sender);
+ outSenders.add(sender);
currentSenderKey = key;
}
currentGroup.add(message);
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5cda3f2..3e065bf 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,8 +16,8 @@
package com.android.internal.widget;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
-import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
import android.annotation.NonNull;
@@ -166,7 +166,7 @@
}
private void setIconToGlue(@Nullable Drawable icon) {
- if (!USE_NEW_ACTION_LAYOUT) {
+ if (!evenlyDividedCallStyleActionLayout()) {
Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
return;
}
@@ -207,7 +207,7 @@
}
private void setLabelToGlue(@Nullable CharSequence label) {
- if (!USE_NEW_ACTION_LAYOUT) {
+ if (!evenlyDividedCallStyleActionLayout()) {
Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
return;
}
@@ -255,7 +255,7 @@
return;
}
- if (!USE_NEW_ACTION_LAYOUT) {
+ if (!evenlyDividedCallStyleActionLayout()) {
Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
return;
}
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
index 85b0201..42de60e 100644
--- a/core/java/com/android/internal/widget/MessagingData.java
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -28,24 +28,33 @@
private final boolean mShowSpinner;
private final List<MessagingMessage> mHistoricMessagingMessages;
private final List<MessagingMessage> mNewMessagingMessages;
+ private final List<List<MessagingMessage>> mGroups;
+ private final List<Person> mSenders;
private final int mUnreadCount;
MessagingData(Person user, boolean showSpinner,
List<MessagingMessage> historicMessagingMessages,
- List<MessagingMessage> newMessagingMessages) {
+ List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups,
+ List<Person> senders) {
this(user, showSpinner, /* unreadCount= */0,
- historicMessagingMessages, newMessagingMessages);
+ historicMessagingMessages, newMessagingMessages,
+ groups,
+ senders);
}
MessagingData(Person user, boolean showSpinner,
int unreadCount,
List<MessagingMessage> historicMessagingMessages,
- List<MessagingMessage> newMessagingMessages) {
+ List<MessagingMessage> newMessagingMessages,
+ List<List<MessagingMessage>> groups,
+ List<Person> senders) {
mUser = user;
mShowSpinner = showSpinner;
mUnreadCount = unreadCount;
mHistoricMessagingMessages = historicMessagingMessages;
mNewMessagingMessages = newMessagingMessages;
+ mGroups = groups;
+ mSenders = senders;
}
public Person getUser() {
@@ -67,4 +76,12 @@
public int getUnreadCount() {
return mUnreadCount;
}
+
+ public List<Person> getSenders() {
+ return mSenders;
+ }
+
+ public List<List<MessagingMessage>> getGroups() {
+ return mGroups;
+ }
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index b6d7503..d000596 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -189,9 +189,15 @@
/* isHistoric= */true, usePrecomputedText);
final List<MessagingMessage> newMessagingMessages =
createMessages(newMessages, /* isHistoric */false, usePrecomputedText);
+ // Let's first find our groups!
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<Person> senders = new ArrayList<>();
+
+ // Lets first find the groups
+ findGroups(historicMessagingMessages, newMessagingMessages, groups, senders);
return new MessagingData(user, showSpinner,
- historicMessagingMessages, newMessagingMessages);
+ historicMessagingMessages, newMessagingMessages, groups, senders);
}
/**
@@ -256,10 +262,10 @@
private void bind(MessagingData messagingData) {
setUser(messagingData.getUser());
- List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
- List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
+ // Let's now create the views and reorder them accordingly
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
- addMessagesToGroups(historicMessages, messages, messagingData.getShowSpinner());
+ createGroupViews(messagingData.getGroups(), messagingData.getSenders(),
+ messagingData.getShowSpinner());
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
@@ -272,8 +278,8 @@
historicMessage.removeMessage(mToRecycle);
}
- mMessages = messages;
- mHistoricMessages = historicMessages;
+ mMessages = messagingData.getNewMessagingMessages();
+ mHistoricMessages = messagingData.getHistoricMessagingMessages();
updateHistoricMessageVisibility();
updateTitleAndNamesDisplay();
@@ -451,19 +457,6 @@
}
}
- private void addMessagesToGroups(List<MessagingMessage> historicMessages,
- List<MessagingMessage> messages, boolean showSpinner) {
- // Let's first find our groups!
- List<List<MessagingMessage>> groups = new ArrayList<>();
- List<Person> senders = new ArrayList<>();
-
- // Lets first find the groups
- findGroups(historicMessages, messages, groups, senders);
-
- // Let's now create the views and reorder them accordingly
- createGroupViews(groups, senders, showSpinner);
- }
-
private void createGroupViews(List<List<MessagingMessage>> groups,
List<Person> senders, boolean showSpinner) {
mGroups.clear();
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 69d2544..301dc39 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -17,7 +17,7 @@
package com.android.internal.widget;
import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
-import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import android.annotation.DimenRes;
import android.app.Notification;
@@ -410,7 +410,7 @@
*/
@RemotableViewMethod
public void setEvenlyDividedMode(boolean evenlyDividedMode) {
- if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+ if (evenlyDividedMode && !evenlyDividedCallStyleActionLayout()) {
Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+ "leaving evenly divided mode disabled");
return;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3fc1683..240028c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -98,6 +98,7 @@
"libminikin",
"libz",
"server_configurable_flags",
+ "android.media.audiopolicy-aconfig-cc",
],
static_libs: [
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 969e47b..070d07c 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -22,6 +22,7 @@
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
#include <android/media/audio/common/AudioConfigBase.h>
+#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
#include <jni.h>
@@ -55,6 +56,8 @@
// ----------------------------------------------------------------------------
+namespace audio_flags = android::media::audiopolicy;
+
using namespace android;
using media::audio::common::AudioConfigBase;
@@ -145,6 +148,7 @@
} gAudioPatchFields;
static jclass gAudioMixClass;
+static jmethodID gAudioMixCstor;
static struct {
jfieldID mRule;
jfieldID mFormat;
@@ -165,7 +169,15 @@
// other fields unused by JNI
} gAudioFormatFields;
+static jclass gAudioAttributesClass;
+static jmethodID gAudioAttributesCstor;
+static struct {
+ jfieldID mSource;
+ jfieldID mUsage;
+} gAudioAttributesFields;
+
static jclass gAudioMixingRuleClass;
+static jmethodID gAudioMixingRuleCstor;
static struct {
jfieldID mCriteria;
jfieldID mAllowPrivilegedPlaybackCapture;
@@ -174,6 +186,8 @@
} gAudioMixingRuleFields;
static jclass gAudioMixMatchCriterionClass;
+static jmethodID gAudioMixMatchCriterionAttrCstor;
+static jmethodID gAudioMixMatchCriterionIntPropCstor;
static struct {
jfieldID mAttr;
jfieldID mIntProp;
@@ -2087,6 +2101,39 @@
channelMask, channelIndexMask);
}
+jint nativeAudioConfigToJavaAudioFormat(JNIEnv *env, const audio_config_t *nConfigBase,
+ jobject *jAudioFormat, bool isInput) {
+ if (!audio_flags::audio_mix_test_api()) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+
+ if (nConfigBase == nullptr) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
+ int channelMask = 0;
+ int channelIndexMask = 0;
+ switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) {
+ case AUDIO_CHANNEL_REPRESENTATION_POSITION:
+ channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask)
+ : outChannelMaskFromNative(nConfigBase->channel_mask);
+ propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+ break;
+ case AUDIO_CHANNEL_REPRESENTATION_INDEX:
+ channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask);
+ propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
+ break;
+ default:
+ // This must not happen
+ break;
+ }
+
+ *jAudioFormat = env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask,
+ audioFormatFromNative(nConfigBase->format),
+ nConfigBase->sample_rate, channelMask, channelIndexMask);
+ return AUDIO_JAVA_SUCCESS;
+}
+
jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes,
audio_mixer_attributes_t *nMixerAttributes) {
ScopedLocalRef<jobject> jFormat(env,
@@ -2179,6 +2226,88 @@
return AUDIO_JAVA_SUCCESS;
}
+static jint nativeAudioMixToJavaAudioMixingRule(JNIEnv *env, const AudioMix &nAudioMix,
+ jobject *jAudioMixingRule) {
+ if (!audio_flags::audio_mix_test_api()) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+
+ jobject jAudioMixMatchCriterionList = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+ for (const auto &criteria : nAudioMix.mCriteria) {
+ jobject jAudioAttributes = NULL;
+ jobject jMixMatchCriterion = NULL;
+ jobject jValueInteger = NULL;
+ switch (criteria.mRule) {
+ case RULE_MATCH_UID:
+ jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUid);
+ jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+ gAudioMixMatchCriterionIntPropCstor,
+ jValueInteger, criteria.mRule);
+ break;
+ case RULE_MATCH_USERID:
+ jValueInteger =
+ env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUserId);
+ jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+ gAudioMixMatchCriterionIntPropCstor,
+ jValueInteger, criteria.mRule);
+ break;
+ case RULE_MATCH_AUDIO_SESSION_ID:
+ jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor,
+ criteria.mValue.mAudioSessionId);
+ jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+ gAudioMixMatchCriterionIntPropCstor,
+ jValueInteger, criteria.mRule);
+ break;
+ case RULE_MATCH_ATTRIBUTE_USAGE:
+ jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
+ env->SetIntField(jAudioAttributes, gAudioAttributesFields.mUsage,
+ criteria.mValue.mUsage);
+ jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+ gAudioMixMatchCriterionAttrCstor,
+ jMixMatchCriterion, criteria.mRule);
+ break;
+ case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+ jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
+ env->SetIntField(jAudioAttributes, gAudioAttributesFields.mSource,
+ criteria.mValue.mSource);
+ jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+ gAudioMixMatchCriterionAttrCstor,
+ jMixMatchCriterion, criteria.mRule);
+ break;
+ }
+ env->CallBooleanMethod(jAudioMixMatchCriterionList, gArrayListMethods.add,
+ jMixMatchCriterion);
+ }
+
+ *jAudioMixingRule = env->NewObject(gAudioMixingRuleClass, gAudioMixingRuleCstor,
+ nAudioMix.mMixType, jAudioMixMatchCriterionList,
+ nAudioMix.mAllowPrivilegedMediaPlaybackCapture,
+ nAudioMix.mVoiceCommunicationCaptureAllowed);
+ return AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioMixFromNative(JNIEnv *env, jobject *jAudioMix, const AudioMix &nAudioMix) {
+ if (!audio_flags::audio_mix_test_api()) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+ jobject jAudioMixingRule = NULL;
+ int status = nativeAudioMixToJavaAudioMixingRule(env, nAudioMix, &jAudioMixingRule);
+ if (status != AUDIO_JAVA_SUCCESS) {
+ return status;
+ }
+ jobject jAudioFormat = NULL;
+ status = nativeAudioConfigToJavaAudioFormat(env, &nAudioMix.mFormat, &jAudioFormat, false);
+ if (status != AUDIO_JAVA_SUCCESS) {
+ return status;
+ }
+
+ jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str());
+ *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat,
+ nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType,
+ deviceAddress);
+ return AUDIO_JAVA_SUCCESS;
+}
+
static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) {
nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
@@ -2252,6 +2381,34 @@
return nativeToJavaStatus(status);
}
+static jint android_media_AudioSystem_getRegisteredPolicyMixes(JNIEnv *env, jobject clazz,
+ jobject jMixes) {
+ if (!audio_flags::audio_mix_test_api()) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+
+ status_t status;
+ std::vector<AudioMix> mixes;
+ ALOGV("AudioSystem::getRegisteredPolicyMixes");
+ status = AudioSystem::getRegisteredPolicyMixes(mixes);
+ ALOGV("AudioSystem::getRegisteredPolicyMixes() returned %zu mixes. Status=%d", mixes.size(),
+ status);
+ if (status != NO_ERROR) {
+ return nativeToJavaStatus(status);
+ }
+
+ for (const auto &mix : mixes) {
+ jobject jAudioMix = NULL;
+ int conversionStatus = convertAudioMixFromNative(env, &jAudioMix, mix);
+ if (conversionStatus != AUDIO_JAVA_SUCCESS) {
+ return conversionStatus;
+ }
+ env->CallBooleanMethod(jMixes, gListMethods.add, jAudioMix);
+ }
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
static jint android_media_AudioSystem_updatePolicyMixes(JNIEnv *env, jobject clazz,
jobjectArray mixes,
jobjectArray updatedMixingRules) {
@@ -3251,6 +3408,8 @@
MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
android_media_AudioSystem_registerPolicyMixes),
+ MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I",
+ android_media_AudioSystem_getRegisteredPolicyMixes),
MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
"([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
"AudioMixingRule;)I",
@@ -3499,6 +3658,11 @@
jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
+ if (audio_flags::audio_mix_test_api()) {
+ gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>",
+ "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
+ "media/AudioFormat;IIILjava/lang/String;)V");
+ }
gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule",
"Landroid/media/audiopolicy/AudioMixingRule;");
gAudioMixFields.mFormat = GetFieldIDOrDie(env, audioMixClass, "mFormat",
@@ -3521,6 +3685,10 @@
jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule");
gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass);
+ if (audio_flags::audio_mix_test_api()) {
+ gAudioMixingRuleCstor = GetMethodIDOrDie(env, audioMixingRuleClass, "<init>",
+ "(ILjava/util/Collection;ZZ)V");
+ }
gAudioMixingRuleFields.mCriteria = GetFieldIDOrDie(env, audioMixingRuleClass, "mCriteria",
"Ljava/util/ArrayList;");
gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture =
@@ -3529,9 +3697,24 @@
gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed =
GetFieldIDOrDie(env, audioMixingRuleClass, "mVoiceCommunicationCaptureAllowed", "Z");
+ if (audio_flags::audio_mix_test_api()) {
+ jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes");
+ gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
+ gAudioAttributesCstor = GetMethodIDOrDie(env, gAudioAttributesClass, "<init>", "()V");
+ gAudioAttributesFields.mSource = GetFieldIDOrDie(env, gAudioAttributesClass, "mUsage", "I");
+ gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, gAudioAttributesClass, "mSource", "I");
+ }
+
jclass audioMixMatchCriterionClass =
FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion");
gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass);
+ if (audio_flags::audio_mix_test_api()) {
+ gAudioMixMatchCriterionAttrCstor =
+ GetMethodIDOrDie(env, gAudioMixMatchCriterionClass, "<init>",
+ "(Landroid/media/AudioAttributes;I)V");
+ gAudioMixMatchCriterionIntPropCstor = GetMethodIDOrDie(env, gAudioMixMatchCriterionClass,
+ "<init>", "(Ljava/lang/Integer;I)V");
+ }
gAudioMixMatchCriterionFields.mAttr = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mAttr",
"Landroid/media/AudioAttributes;");
gAudioMixMatchCriterionFields.mIntProp = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mIntProp",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e325a5..0938ce17 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -667,8 +667,9 @@
}
}
-static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn, jlong bounding_capabilities) {
for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if ((1LL << i) & bounding_capabilities) continue;
if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
@@ -680,6 +681,27 @@
}
}
+static bool MatchGid(JNIEnv* env, jintArray gids, jint gid, jint gid_to_find) {
+ if (gid == gid_to_find) return true;
+
+ if (gids == nullptr) return false;
+
+ jsize gids_num = env->GetArrayLength(gids);
+ ScopedIntArrayRO native_gid_proxy(env, gids);
+
+ if (native_gid_proxy.get() == nullptr) {
+ RuntimeAbort(env, __LINE__, "Bad gids array");
+ }
+
+ for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
+ if (native_gid_proxy[gids_index] == gid_to_find) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
@@ -1875,9 +1897,9 @@
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jlong permitted_capabilities,
- jlong effective_capabilities, jint mount_external,
- jstring managed_se_info, jstring managed_nice_name,
- bool is_system_server, bool is_child_zygote,
+ jlong effective_capabilities, jlong bounding_capabilities,
+ jint mount_external, jstring managed_se_info,
+ jstring managed_nice_name, bool is_system_server, bool is_child_zygote,
jstring managed_instruction_set, jstring managed_app_data_dir,
bool is_top_app, jobjectArray pkg_data_info_list,
jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
@@ -1891,6 +1913,9 @@
auto instruction_set = extract_fn(managed_instruction_set);
auto app_data_dir = extract_fn(managed_app_data_dir);
+ // Permit bounding capabilities
+ permitted_capabilities |= bounding_capabilities;
+
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
EnableKeepCapabilities(fail_fn);
@@ -1898,7 +1923,7 @@
SetInheritable(permitted_capabilities, fail_fn);
- DropCapabilitiesBoundingSet(fail_fn);
+ DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);
bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
android::NativeBridgeAvailable() &&
@@ -2165,6 +2190,23 @@
return capdata[0].effective | (static_cast<uint64_t>(capdata[1].effective) << 32);
}
+static jlong CalculateBoundingCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids) {
+ jlong capabilities = 0;
+
+ /*
+ * Grant CAP_SYS_NICE to CapInh/CapPrm/CapBnd for processes that can spawn
+ * VMs. This enables processes to execve on binaries with elevated
+ * capabilities if its file capability bits are set. This does not grant
+ * capability to the parent process(that spawns the VM) as the effective
+ * bits are not set.
+ */
+ if (MatchGid(env, gids, gid, AID_VIRTUALMACHINE)) {
+ capabilities |= (1LL << CAP_SYS_NICE);
+ }
+
+ return capabilities;
+}
+
static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids,
bool is_child_zygote) {
jlong capabilities = 0;
@@ -2198,26 +2240,7 @@
* Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
*/
- bool gid_wakelock_found = false;
- if (gid == AID_WAKELOCK) {
- gid_wakelock_found = true;
- } else if (gids != nullptr) {
- jsize gids_num = env->GetArrayLength(gids);
- ScopedIntArrayRO native_gid_proxy(env, gids);
-
- if (native_gid_proxy.get() == nullptr) {
- RuntimeAbort(env, __LINE__, "Bad gids array");
- }
-
- for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
- if (native_gid_proxy[gids_index] == AID_WAKELOCK) {
- gid_wakelock_found = true;
- break;
- }
- }
- }
-
- if (gid_wakelock_found) {
+ if (MatchGid(env, gids, gid, AID_WAKELOCK)) {
capabilities |= (1LL << CAP_BLOCK_SUSPEND);
}
@@ -2393,6 +2416,11 @@
const std::vector<int>& fds_to_ignore,
bool is_priority_fork,
bool purge) {
+ ATRACE_CALL();
+ if (is_priority_fork) {
+ setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
+ }
+
SetSignalHandlers();
// Curry a failure function.
@@ -2478,6 +2506,10 @@
// We blocked SIGCHLD prior to a fork, we unblock it here.
UnblockSignal(SIGCHLD, fail_fn);
+ if (is_priority_fork && pid != 0) {
+ setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);
+ }
+
return pid;
}
@@ -2494,6 +2526,7 @@
jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
zygote::ZygoteFailure(env, "zygote", nice_name,
@@ -2532,10 +2565,11 @@
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
return pid;
}
@@ -2545,6 +2579,7 @@
JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
jlong effective_capabilities) {
+ ATRACE_CALL();
std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
fds_to_ignore(fds_to_close);
@@ -2568,7 +2603,7 @@
// System server prcoess does not need data isolation so no need to
// know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
- effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+ effective_capabilities, 0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
/* pkg_data_info_list */ nullptr,
/* allowlisted_data_info_list */ nullptr, false, false, false);
@@ -2620,6 +2655,7 @@
jintArray managed_session_socket_fds,
jboolean args_known,
jboolean is_priority_fork) {
+ ATRACE_CALL();
std::vector<int> session_socket_fds =
ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds)
.value_or(std::vector<int>());
@@ -2635,6 +2671,7 @@
bool args_known,
bool is_priority_fork,
bool purge) {
+ ATRACE_CALL();
std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
fds_to_ignore(fds_to_close);
@@ -2725,12 +2762,14 @@
jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
/**
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 381580b..c65794e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -19,6 +19,7 @@
per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
+per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
# Biometrics
jaggies@google.com
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 104c023..10f75d0 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -612,6 +612,7 @@
}
optional Sounds sounds = 72;
+ optional SettingProto stylus_pointer_icon_enabled = 99 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto swipe_bottom_to_notification_enabled = 82 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Defines whether managed profile ringtones should be synced from its
// parent profile.
@@ -720,5 +721,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 99;
+ // Next tag = 100;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c71a842..374c312 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3216,6 +3216,13 @@
android:description="@string/permdesc_accessHiddenProfile"
android:protectionLevel="normal" />
+ <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile
+ users.
+ @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") -->
+ <permission
+ android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
@@ -7555,6 +7562,11 @@
<permission android:name="android.permission.RESET_APP_ERRORS"
android:protectionLevel="signature" />
+ <!-- @hide Allows ThemeOverlayController to delay launch of Home / SetupWizard on boot, ensuring
+ Theme Palettes and Colors are ready -->
+ <permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY"
+ android:protectionLevel="signature|setup" />
+
<!-- @hide Allows an application to create/destroy input consumer. -->
<permission android:name="android.permission.INPUT_CONSUMER"
android:protectionLevel="signature" />
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index ddedca2..50ff7c9 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -79,16 +79,16 @@
android:layout_height="1dp"
style="@style/AutofillHalfSheetDivider" />
- <com.android.internal.widget.ButtonBarLayout
+ <com.android.server.autofill.ui.BottomSheetButtonBarLayout
+ android:id="@+id/autofill_save_button_bar"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:layout_gravity="end"
android:clipToPadding="false"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
android:orientation="horizontal"
- android:gravity="center_vertical"
android:layout_marginStart="@dimen/autofill_save_outer_margin"
android:layout_marginEnd="@dimen/autofill_save_outer_margin"
>
@@ -100,12 +100,15 @@
android:paddingEnd="12dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
android:minWidth="0dp"
style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
<Space
+ android:id="@+id/autofill_button_bar_spacer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
@@ -116,12 +119,14 @@
android:id="@+id/autofill_save_yes"
android:layout_width="wrap_content"
android:layout_height="40dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
android:minWidth="0dp"
style="@style/AutofillHalfSheetTonalButton"
android:text="@string/autofill_save_yes">
</Button>
- </com.android.internal.widget.ButtonBarLayout>
+ </com.android.server.autofill.ui.BottomSheetButtonBarLayout>
</com.android.server.autofill.ui.BottomSheetLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e7b1d09..908eeeb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2292,7 +2292,17 @@
{@link android.R.attr#windowDrawsSystemBarBackgrounds} and the status bar must not
have been requested to be translucent with
{@link android.R.attr#windowTranslucentStatus}.
- Corresponds to {@link android.view.Window#setStatusBarColor(int)}. -->
+ Corresponds to {@link android.view.Window#setStatusBarColor(int)}.
+ <p>If the color is transparent and the window enforces the status bar contrast, the
+ system will determine whether a scrim is necessary and draw one on behalf of the app to
+ ensure that the status bar has enough contrast with the contents of this app, and set
+ an appropriate effective bar background accordingly.
+ See: {@link android.R.attr#enforceStatusBarContrast}
+ <p>If the app targets
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ this attribute is ignored.
+ @deprecated Draw proper background behind
+ {@link android.view.WindowInsets.Type#statusBars()}} instead. -->
<attr name="statusBarColor" format="color" />
<!-- The color for the navigation bar. If the color is not opaque, consider setting
@@ -2302,7 +2312,18 @@
{@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
- Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. -->
+ Corresponds to {@link android.view.Window#setNavigationBarColor(int)}.
+ <p>If the color is transparent and the window enforces the navigation bar contrast, the
+ system will determine whether a scrim is necessary and draw one on behalf of the app to
+ ensure that the navigation bar has enough contrast with the contents of this app, and
+ set an appropriate effective bar background accordingly.
+ See: {@link android.R.attr#enforceNavigationBarContrast}
+ <p>If the app targets
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ this attribute is ignored.
+ @deprecated Draw proper background behind
+ {@link android.view.WindowInsets.Type#navigationBars()} or
+ {@link android.view.WindowInsets.Type#tappableElement()} instead. -->
<attr name="navigationBarColor" format="color" />
<!-- Shows a thin line of the specified color between the navigation bar and the app
@@ -2311,7 +2332,13 @@
{@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
- Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. -->
+ Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
+ <p>If the app targets
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+ this attribute is ignored.
+ @deprecated Draw proper background behind
+ {@link android.view.WindowInsets.Type#navigationBars()} or
+ {@link android.view.WindowInsets.Type#tappableElement()} instead. -->
<attr name="navigationBarDividerColor" format="color" />
<!-- Sets whether the system should ensure that the status bar has enough
@@ -2327,7 +2354,9 @@
<p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},
this attribute is ignored.
- @see android.view.Window#setStatusBarContrastEnforced -->
+ @see android.view.Window#setStatusBarContrastEnforced
+ @deprecated Draw proper background behind
+ {@link android.view.WindowInsets.Type#statusBars()}} instead. -->
<attr name="enforceStatusBarContrast" format="boolean" />
<!-- Sets whether the system should ensure that the navigation bar has enough
@@ -2483,6 +2512,31 @@
<!-- The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY -->
<enum name="icon_preferred" value="1" />
</attr>
+
+ <!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
+
+ <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
+ app targets
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
+ The affected behaviors are:
+ <ul>
+ <li>The framework will not fit the content view to the insets and will just pass
+ through the {@link android.view.WindowInsets} to the content view, as if calling
+ {@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
+ <li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
+ the non-floating windows will be set to {@link
+ android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
+ Changing it to other values will cause {@link lang.IllegalArgumentException}.
+ <li>The framework will set {@link android.R.attr#statusBarColor},
+ {@link android.R.attr#navigationBarColor}, and
+ {@link android.R.attr#navigationBarDividerColor} to transparent.
+ </ul>
+
+ <p>If this is true, the edge-to-edge enforcement won't be applied. However, this
+ attribute will be deprecated and disabled in a future SDK level.
+
+ <p>This is false by default. -->
+ <attr name="windowOptOutEdgeToEdgeEnforcement" format="boolean"/>
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d910940..4741012 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3278,6 +3278,31 @@
<p> By default, the behavior is configured by the same attribute in application.
-->
<attr name="enableOnBackInvokedCallback" format="boolean"/>
+
+ <!-- Specifies permissions necessary to launch this activity via
+ {@link android.content.Context#startActivity} when passing content URIs. The default
+ value is {@code none}, meaning no specific permissions are required. Setting this
+ attribute restricts activity invocation based on the invoker's permissions. If the
+ invoker doesn't have the required permissions, the activity start will be denied via a
+ {@link java.lang.SecurityException}.
+
+ <p> Note that the enforcement works for content URIs inside
+ {@link android.content.Intent#getData} and {@link android.content.Intent#getClipData}.
+ @FlaggedApi("android.security.content_uri_permission_apis") -->
+ <attr name="requireContentUriPermissionFromCaller" format="string">
+ <!-- Default, no specific permissions are required. -->
+ <enum name="none" value="0" />
+ <!-- Enforces the invoker to have read access to the passed content URIs. -->
+ <enum name="read" value="1" />
+ <!-- Enforces the invoker to have write access to the passed content URIs. -->
+ <enum name="write" value="2" />
+ <!-- Enforces the invoker to have either read or write access to the passed content
+ URIs. -->
+ <enum name="readOrWrite" value="3" />
+ <!-- Enforces the invoker to have both read and write access to the passed content
+ URIs. -->
+ <enum name="readAndWrite" value="4" />
+ </attr>
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 96c4bf4..0acccee 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -887,6 +887,8 @@
<dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
<dimen name="autofill_save_button_bar_padding">16dp</dimen>
<dimen name="autofill_dialog_corner_radius">24dp</dimen>
+ <dimen name="autofill_button_bar_spacer_width">12dp</dimen>
+ <dimen name="autofill_button_bar_spacer_height">4dp</dimen>
<!-- How much extra space should be left around the autofill dialog -->
<dimen name="autofill_dialog_offset">72dp</dimen>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 81a8908..f9cf28c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -147,6 +147,10 @@
<public name="useBoundsForWidth"/>
<!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
<public name="autoTransact"/>
+ <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
+ <public name="windowOptOutEdgeToEdgeEnforcement"/>
+ <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
+ <public name="requireContentUriPermissionFromCaller" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4cf37df..58427b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3728,6 +3728,7 @@
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_yes" />
+ <java-symbol type="id" name="autofill_button_bar_spacer" />
<java-symbol type="id" name="autofill_service_icon" />
<java-symbol type="id" name="autofill_dialog_picker"/>
<java-symbol type="id" name="autofill_dialog_header"/>
@@ -3774,6 +3775,8 @@
<java-symbol type="dimen" name="autofill_dialog_max_width" />
<java-symbol type="dimen" name="autofill_dialog_offset"/>
<java-symbol type="dimen" name="autofill_save_outer_margin"/>
+ <java-symbol type="dimen" name="autofill_button_bar_spacer_width"/>
+ <java-symbol type="dimen" name="autofill_button_bar_spacer_height"/>
<java-symbol type="bool" name="autofill_dialog_horizontal_space_included"/>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 9bb2499..61e6a36 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -127,7 +127,7 @@
<!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements,
visual voicemail code for Orange: 21101 -->
- <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051" />
+ <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051|33033" />
<!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
@@ -150,6 +150,9 @@
http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
<shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
+ <!-- Honduras -->
+ <shortcode country="hn" pattern="\\d{4,6}" free="466453" />
+
<!-- India: 1-5 digits (standard system default, not country specific) -->
<shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
@@ -171,7 +174,7 @@
<shortcode country="jp" pattern="\\d{1,5}" free="8083" />
<!-- Kenya: 5 digits, known premium codes listed -->
- <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" />
+ <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023|24088|23054" />
<!-- Kyrgyzstan: 4 digits, known premium codes listed -->
<shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
@@ -183,7 +186,7 @@
<shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
<!-- Kuwait: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991" />
+ <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991|50976" />
<!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
<shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399|1324" />
@@ -195,9 +198,18 @@
<!-- Latvia: 4 digits, known premium codes listed, plus EU -->
<shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" />
+ <!-- Morocco: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="ma" pattern="\\d{1,5}" free="53819" />
+
<!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed -->
<shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
+ <!-- Malawi: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="mw" pattern="\\d{1,5}" free="4276" />
+
+ <!-- Mozambique: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="mz" pattern="\\d{1,5}" free="1714" />
+
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
@@ -207,6 +219,9 @@
<!-- Namibia: 1-5 digits (standard system default, not country specific) -->
<shortcode country="na" pattern="\\d{1,5}" free="40005" />
+ <!-- Nicaragua -->
+ <shortcode country="ni" pattern="\\d{4,6}" free="466453" />
+
<!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
<shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
@@ -219,8 +234,8 @@
<!-- New Zealand: 3-4 digits, known premium codes listed -->
<shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
- <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
+ <!-- Peru: 4-6 digits (not confirmed), known premium codes listed -->
+ <shortcode country="pe" pattern="\\d{4,6}" free="9963|40778|301303" />
<!-- Philippines -->
<shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
@@ -269,6 +284,12 @@
<!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
<shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" />
+ <!-- Senegal(SN): 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="sn" pattern="\\d{1,5}" free="21215" />
+
+ <!-- El Salvador(SV): 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="sv" pattern="\\d{4,6}" free="466453" />
+
<!-- Taiwan -->
<shortcode country="tw" pattern="\\d{4}" free="1922" />
@@ -278,15 +299,21 @@
<!-- Tajikistan: 4 digits, known premium codes listed -->
<shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
+ <!-- Tanzania: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" />
+
<!-- Turkey -->
<shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" />
<!-- Ukraine: 4 digits, known premium codes listed -->
<shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
+ <!-- Uganda(UG): 4 digits (standard system default, not country specific) -->
+ <shortcode country="ug" pattern="\\d{4}" free="8000" />
+
<!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm),
visual voicemail code for T-Mobile: 122 -->
- <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611" />
+ <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" />
<!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
<shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" />
diff --git a/core/tests/coretests/src/android/app/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java
new file mode 100644
index 0000000..7021187
--- /dev/null
+++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Semaphore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class QueuedWorkTest {
+
+ private QueuedWork mQueuedWork;
+ private AtomicInteger mCounter;
+
+ private class AddToCounter implements Runnable {
+ private final int mDelta;
+
+ public AddToCounter(int delta) {
+ mDelta = delta;
+ }
+
+ @Override
+ public void run() {
+ mCounter.addAndGet(mDelta);
+ }
+ }
+
+ private class IncrementCounter extends AddToCounter {
+ public IncrementCounter() {
+ super(1);
+ }
+ }
+
+ @Before
+ public void setup() {
+ mQueuedWork = new QueuedWork();
+ mCounter = new AtomicInteger(0);
+ }
+
+ @After
+ public void teardown() {
+ mQueuedWork.waitToFinish();
+ QueuedWork.resetHandler();
+ }
+
+ @Test
+ public void testQueueThenWait() {
+ mQueuedWork.queue(new IncrementCounter(), false);
+ mQueuedWork.waitToFinish();
+ assertThat(mCounter.get()).isEqualTo(1);
+ assertThat(mQueuedWork.hasPendingWork()).isFalse();
+ }
+
+ @Test
+ public void testQueueWithDelayThenWait() {
+ mQueuedWork.queue(new IncrementCounter(), true);
+ mQueuedWork.waitToFinish();
+ assertThat(mCounter.get()).isEqualTo(1);
+ assertThat(mQueuedWork.hasPendingWork()).isFalse();
+ }
+
+ @Test
+ public void testWorkHappensNotOnCallerThread() {
+ AtomicBoolean childThreadStarted = new AtomicBoolean(false);
+ InheritableThreadLocal<Boolean> setTrueInChild =
+ new InheritableThreadLocal<Boolean>() {
+ @Override
+ protected Boolean initialValue() {
+ return false;
+ }
+
+ @Override
+ protected Boolean childValue(Boolean parentValue) {
+ childThreadStarted.set(true);
+ return true;
+ }
+ };
+
+ // Enqueue work to force a worker thread to be created
+ setTrueInChild.get();
+ assertThat(childThreadStarted.get()).isFalse();
+ mQueuedWork.queue(() -> setTrueInChild.get(), false);
+ mQueuedWork.waitToFinish();
+ assertThat(childThreadStarted.get()).isTrue();
+ }
+
+ @Test
+ public void testWaitToFinishDoesNotCreateThread() {
+ InheritableThreadLocal<Boolean> throwInChild =
+ new InheritableThreadLocal<Boolean>() {
+ @Override
+ protected Boolean initialValue() {
+ return false;
+ }
+
+ @Override
+ protected Boolean childValue(Boolean parentValue) {
+ throw new RuntimeException("New thread should not be started!");
+ }
+ };
+
+ try {
+ throwInChild.get();
+ // Intentionally don't enqueue work.
+ mQueuedWork.waitToFinish();
+ throwInChild.get();
+ // If a worker thread was unnecessarily started, we will have crashed.
+ } finally {
+ throwInChild.remove();
+ }
+ }
+
+ @Test
+ public void testFinisher() {
+ mQueuedWork.addFinisher(new AddToCounter(3));
+ mQueuedWork.addFinisher(new AddToCounter(7));
+ mQueuedWork.queue(new IncrementCounter(), false);
+ mQueuedWork.waitToFinish();
+ // The queued task and the two finishers all ran
+ assertThat(mCounter.get()).isEqualTo(1 + 3 + 7);
+ }
+
+ @Test
+ public void testRemoveFinisher() {
+ Runnable addThree = new AddToCounter(3);
+ Runnable addSeven = new AddToCounter(7);
+ mQueuedWork.addFinisher(addThree);
+ mQueuedWork.addFinisher(addSeven);
+ mQueuedWork.removeFinisher(addThree);
+ mQueuedWork.queue(new IncrementCounter(), false);
+ mQueuedWork.waitToFinish();
+ // The queued task and the two finishers all ran
+ assertThat(mCounter.get()).isEqualTo(1 + 7);
+ }
+
+ @Test
+ public void testHasPendingWork() {
+ Semaphore releaser = new Semaphore(0);
+ mQueuedWork.queue(
+ () -> {
+ try {
+ releaser.acquire();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }, false);
+ assertThat(mQueuedWork.hasPendingWork()).isTrue();
+ releaser.release();
+ mQueuedWork.waitToFinish();
+ assertThat(mQueuedWork.hasPendingWork()).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 36bb8e5..548b8ec 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -104,7 +104,9 @@
private void createComplexDatabase() {
mDatabase.beginTransaction();
try {
- mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+ // Column "l" is used to test the long variants. The underlying sqlite type is int,
+ // which is the same as a java long.
+ mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text, l int);");
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
@@ -115,7 +117,7 @@
* A three-value insert for the complex database.
*/
private String createComplexInsert() {
- return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+ return "INSERT INTO t1 (i, d, t, l) VALUES (?1, ?2, ?3, ?4)";
}
/**
@@ -209,22 +211,25 @@
mDatabase.beginTransaction();
try {
try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < 9; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
+ for (int row = 0; row < 9; row++) {
+ int vi = row * 3;
+ double vd = row * 2.5;
+ String vt = String.format("text%02dvalue", row);
+ long vl = Long.MAX_VALUE - row;
s.bindInt(1, vi);
s.bindDouble(2, vd);
s.bindText(3, vt);
+ s.bindLong(4, vl);
boolean r = s.step();
// No row is returned by this query.
assertFalse(r);
s.reset();
}
- // The last row has a null double and a null text.
+ // The last row has a null double, null text, and null long.
s.bindInt(1, 20);
s.bindNull(2);
s.bindNull(3);
+ s.bindNull(4);
assertFalse(s.step());
s.reset();
}
@@ -248,19 +253,31 @@
mDatabase.endTransaction();
}
- // Verify that the element created with i == 3 is correct.
+ // Verify that the element created with row == 3 is correct.
mDatabase.beginTransactionReadOnly();
try {
- final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
+ final String query = "SELECT i, d, t, l FROM t1 WHERE t = 'text03value'";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnCount());
+ assertEquals(4, s.getResultColumnCount());
int vi = s.getColumnInt(0);
double vd = s.getColumnDouble(1);
String vt = s.getColumnText(2);
- assertEquals(3 * 3, vi);
- assertEquals(2.5 * 3, vd, 0.1);
+ long vl = s.getColumnLong(3);
+ // The query extracted the third generated row.
+ final int row = 3;
+ assertEquals(3 * row, vi);
+ assertEquals(2.5 * row, vd, 0.1);
assertEquals("text03value", vt);
+ assertEquals(Long.MAX_VALUE - row, vl);
+
+ // Verify the column types. Remember that sqlite integers are the same as Java
+ // long, so the integer and long columns have type INTEGER.
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_FLOAT, s.getColumnType(1));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(2));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(3));
+
// No more rows.
assertFalse(s.step());
}
@@ -268,15 +285,24 @@
mDatabase.endTransaction();
}
+ // Verify that null columns are returned properly.
mDatabase.beginTransactionReadOnly();
try {
- final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
+ final String query = "SELECT i, d, t, l FROM t1 WHERE i == 20";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnCount());
+ assertEquals(4, s.getResultColumnCount());
assertEquals(20, s.getColumnInt(0));
assertEquals(0.0, s.getColumnDouble(1), 0.01);
assertEquals(null, s.getColumnText(2));
+ assertEquals(0, s.getColumnLong(3));
+
+ // Verify the column types.
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(1));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(2));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(3));
+
// No more rows.
assertFalse(s.step());
}
@@ -495,6 +521,8 @@
// Fetch the entire reference array.
s.bindInt(1, 1);
assertTrue(s.step());
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_BLOB, s.getColumnType(0));
+
byte[] a = s.getColumnBlob(0);
assertTrue(Arrays.equals(src, a));
s.reset();
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
index a525615..c18f35f 100644
--- a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -126,4 +126,4 @@
TextLine.recycle(tl)
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 27869bb..71980c1 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -21,7 +21,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -40,7 +40,7 @@
@JvmField
val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
@Test
fun calculateRunFlagTest() {
// Only one Bidi run
@@ -84,7 +84,7 @@
.isEqualTo(LEFT_EDGE)
}
- @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
@Test
fun resolveRunFlagForSubSequenceTest() {
val runStart = 5
@@ -221,4 +221,4 @@
MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
.isEqualTo(MIDDLE_OF_LINE)
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index de55b07..3df3b9d2 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@@ -63,7 +64,8 @@
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
+ if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && !mPhoneWindow.isFloating()) {
assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
} else {
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 4a9dd2f..3b1867c 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -21,12 +21,36 @@
Example usage:
- <enhanced-confirmation-trusted-installer
+ <enhanced-confirmation-trusted-package
package="com.example.app"
- signature="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+ sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-This indicates that "com.example.app" should be exempt from ECM, and that, if "com.example.app" is
-an installer, all packages installed via "com.example.app" will also be exempt from ECM.
+ ...
+
+ <enhanced-confirmation-trusted-installer
+ package="com.example.installer"
+ sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+ ...
+
+The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
+should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+
+The "enhanced-confirmation-trusted-installer" entry shown above indicates that
+"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
+packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
+For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
+restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+
+In either case:
+
+- The "package" XML attribute refers to the app's package name.
+- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
+
+For any entry to successfully apply to a package, both XML attributes must be present, and must
+match the package. That is, the package name must match the "package" attribute, and the app must be
+signed by the signing certificate identified by the "sha256-cert-digest" attribute..
-->
<config></config>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 13d38d2..9d1e507 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -124,6 +124,10 @@
<group gid="security_log_writer" />
</permission>
+ <permission name="android.permission.MANAGE_VIRTUAL_MACHINE">
+ <group gid="virtualmachine" />
+ </permission>
+
<!-- These are permissions that were mapped to gids but we need
to keep them here until an upgrade from L to the current
version is to be supported. These permissions are built-in
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index bf60944..7823277 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -108,6 +108,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Settings (Settings app) -->
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ae61a2d..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,7 +17,7 @@
package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -293,7 +293,7 @@
* {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
* {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
@@ -324,7 +324,7 @@
* {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
* {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
// These flags are always set on a new/reset paint, even if flags 0 is passed.
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 9707126..0e9f29d 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,7 +17,7 @@
package android.graphics.text;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -183,7 +183,7 @@
/**
* Value for justification mode indicating the text is justified by stretching letter spacing.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
/**
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 2beb434..2430e8d 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.StrictMode;
@@ -218,4 +219,28 @@
return SYSTEM_ERROR;
}
}
+
+ /**
+ * Returns the list of Application UIDs that have auth-bound keys that are bound to
+ * the given SID. This enables warning the user when they are about to invalidate
+ * a SID (for example, removing the LSKF).
+ *
+ * @param userId - The ID of the user the SID is associated with.
+ * @param userSecureId - The SID in question.
+ *
+ * @return A list of app UIDs.
+ */
+ public static long[] getAllAppUidsAffectedBySid(int userId, long userSecureId)
+ throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+ try {
+ return getService().getAppUidsAffectedBySid(userId, userSecureId);
+ } catch (RemoteException | NullPointerException e) {
+ throw new KeyStoreException(SYSTEM_ERROR,
+ "Failure to connect to Keystore while trying to get apps affected by SID.");
+ } catch (ServiceSpecificException e) {
+ throw new KeyStoreException(e.errorCode,
+ "Keystore error while trying to get apps affected by SID.");
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 9854e58..a80afe2 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,9 +45,6 @@
<!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
<bool name="config_pipEnableResizeForMenu">true</bool>
- <!-- Allow PIP to resize via dragging the corner of PiP. -->
- <bool name="config_pipEnableDragCornerResize">false</bool>
-
<!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
<fraction name="config_pipShortestEdgePercent">40%</fraction>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 160f922..55982dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -310,12 +310,16 @@
float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, mEnteringProgress, 1.0f);
-
+ float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f);
mEnteringRect.set(left, top, left + width, top + height);
applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
}
+ private float getPreCommitEnteringAlpha() {
+ return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getEnteringProgress() {
return mEnteringProgress * SCALE_FACTOR;
}
@@ -325,9 +329,7 @@
if (mEnteringTarget != null && mEnteringTarget.leash != null) {
transformWithProgress(
mEnteringProgress,
- Math.max(
- smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
- MIN_WINDOW_ALPHA), /* alpha */
+ getPreCommitEnteringAlpha(),
mEnteringTarget.leash,
mEnteringRect,
-mWindowXShift,
@@ -336,6 +338,11 @@
}
}
+ private float getPreCommitLeavingAlpha() {
+ return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getLeavingProgress() {
return mLeavingProgress * SCALE_FACTOR;
}
@@ -345,9 +352,7 @@
if (mClosingTarget != null && mClosingTarget.leash != null) {
transformWithProgress(
mLeavingProgress,
- Math.max(
- 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
- MIN_WINDOW_ALPHA),
+ getPreCommitLeavingAlpha(),
mClosingTarget.leash,
mClosingRect,
0,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index a2a2914..7a4ad0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -912,9 +912,8 @@
if (uid != -1) {
intent.putExtra(Settings.EXTRA_APP_UID, uid);
}
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 621c453..0aa8959 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -919,6 +919,9 @@
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
+ } else if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
}
}
@@ -1403,6 +1406,13 @@
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
+
+ if (!mBubbleData.hasBubbles()) {
+ // if there are no bubbles, don't create the stack or layer views. they will be created
+ // later when the first bubble is added.
+ return;
+ }
+
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
@@ -1732,6 +1742,10 @@
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
mLayerView.removeBubble(removedBubble);
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b95d258..62f2726 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -273,6 +273,9 @@
if (endAction != null) {
endAction.run();
}
+ if (mBubbleData.getBubbles().isEmpty()) {
+ mBubbleController.onAllBubblesAnimatedOut();
+ }
};
if (mDragController != null && mDragController.isStuckToDismiss()) {
mAnimationHelper.animateDismiss(runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 8b6c7b6..68d26da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -140,7 +140,7 @@
// spec takes the aspect ratio of the bounds into account, so both width and height
// scale by the same factor.
addPipExclusionBoundsChangeCallback((bounds) -> {
- mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+ updateBoundsScale();
});
}
@@ -152,6 +152,11 @@
mSizeSpecSource.onConfigurationChanged();
}
+ /** Update the bounds scale percentage value. */
+ public void updateBoundsScale() {
+ mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f);
+ }
+
private void reloadResources() {
mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 52a06e0..9f73f1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1891,6 +1891,9 @@
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removeContentOverlay: %s, state=%s, surface=%s",
+ mTaskInfo, mPipTransitionState, surface);
if (mPipOverlay != null) {
if (mPipOverlay != surface) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 896ca96..e018ecc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -286,12 +286,6 @@
// For transition that we don't animate, but contains the PIP leash, we need to update the
// PIP surface, otherwise it will be reset after the transition.
if (currentPipTaskChange != null) {
- // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
- // changing the *finish*Transaction, we need to use the end bounds. This will also
- // make sure that the fade-in animation (below) uses the end bounds as well.
- if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
- mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
- }
updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
finishTransaction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index c5a0102..05d4f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -244,6 +244,9 @@
// The same rotation may have been set by auto PiP-able or fixed rotation. So notify
// the change with fromRotation=false to apply the rotated destination bounds from
// PipTaskOrganizer#onMovementBoundsChanged.
+ // We need to update the bounds scale in case this was from fixed rotation, as the
+ // current proportion was computed using the previous orientation max size and is wrong.
+ mPipBoundsState.updateBoundsScale();
updateMovementBounds(null, false /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
return;
@@ -797,21 +800,15 @@
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- // Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
- if (densityDpiChanged) {
- final float scale = (float) layout.densityDpi()
- / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
- postChangeBounds.set(0, 0,
- (int) (postChangeBounds.width() * scale),
- (int) (postChangeBounds.height() * scale));
- }
-
updateDisplayLayout.run();
+ // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+ // example, if PiP was resized to 90% of the maximum size on the previous layout,
+ // make sure it is 90% of the new maximum size spec.
+ postChangeBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -827,6 +824,10 @@
mPipBoundsState.setHasUserResizedPip(true);
mTouchHandler.setUserResizeBounds(postChangeBounds);
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
@@ -1146,6 +1147,11 @@
// Update the display layout
mPipDisplayLayoutState.rotateTo(toRotation);
+ mTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+
+ postChangeStackBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index f175775..5f9195a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,19 +15,13 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Looper;
import android.view.BatchedInputEventReceiver;
@@ -41,7 +35,6 @@
import androidx.annotation.VisibleForTesting;
-import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -53,7 +46,6 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -77,7 +69,6 @@
private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
private final int mDisplayId;
private final ShellExecutor mMainExecutor;
- private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
private final PointF mDownSecondPoint = new PointF();
@@ -88,24 +79,15 @@
private final Rect mLastResizeBounds = new Rect();
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
- private final Rect mDragCornerSize = new Rect();
- private final Rect mTmpTopLeftCorner = new Rect();
- private final Rect mTmpTopRightCorner = new Rect();
- private final Rect mTmpBottomLeftCorner = new Rect();
- private final Rect mTmpBottomRightCorner = new Rect();
- private final Rect mDisplayBounds = new Rect();
- private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private final Consumer<Rect> mUpdateResizeBoundsCallback;
- private int mDelta;
private float mTouchSlop;
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnablePinchResize;
- private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
@@ -123,7 +105,7 @@
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
PipDismissTargetHandler pipDismissTargetHandler,
- Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+ Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
ShellExecutor mainExecutor) {
mContext = context;
@@ -135,7 +117,6 @@
mPipTouchState = pipTouchState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipDismissTargetHandler = pipDismissTargetHandler;
- mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
@@ -171,20 +152,9 @@
}
private void reloadResources() {
- final Resources res = mContext.getResources();
- mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
- private void resetDragCorners() {
- mDragCornerSize.set(0, 0, mDelta, mDelta);
- mTmpTopLeftCorner.set(mDragCornerSize);
- mTmpTopRightCorner.set(mDragCornerSize);
- mTmpBottomLeftCorner.set(mDragCornerSize);
- mTmpBottomRightCorner.set(mDragCornerSize);
- }
-
private void disposeInputChannel() {
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
@@ -232,7 +202,7 @@
@VisibleForTesting
void onInputEvent(InputEvent ev) {
- if (!mEnableDragCornerResize && !mEnablePinchResize) {
+ if (!mEnablePinchResize) {
// No need to handle anything if neither form of resizing is enabled.
return;
}
@@ -260,8 +230,6 @@
if (mEnablePinchResize && mOngoingPinchToResize) {
onPinchResize(mv);
- } else if (mEnableDragCornerResize) {
- onDragCornerResize(mv);
}
}
}
@@ -273,48 +241,6 @@
return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
}
- /**
- * Check whether the current x,y coordinate is within the region in which drag-resize should
- * start.
- * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
- * overlaps with the PIP window while the rest goes outside of the PIP window.
- * _ _ _ _
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- * | PIP |
- * | WINDOW |
- * _|_ _|_
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- */
- public boolean isWithinDragResizeRegion(int x, int y) {
- if (!mEnableDragCornerResize) {
- return false;
- }
-
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- if (currentPipBounds == null) {
- return false;
- }
- resetDragCorners();
- mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
- mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
-
- mTmpRegion.setEmpty();
- mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
-
- return mTmpRegion.contains(x, y);
- }
-
public boolean isUsingPinchToZoom() {
return mEnablePinchResize;
}
@@ -325,62 +251,17 @@
public boolean willStartResizeGesture(MotionEvent ev) {
if (isInValidSysUiState()) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
- return true;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- onPinchResize(ev);
- mOngoingPinchToResize = mAllowGesture;
- return mAllowGesture;
- }
- break;
-
- default:
- break;
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
}
}
return false;
}
- private void setCtrlType(int x, int y) {
- final Rect currentPipBounds = mPipBoundsState.getBounds();
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
-
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
-
- if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpBottomRightCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_BOTTOM;
- }
- if (mTmpBottomLeftCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_BOTTOM;
- }
- }
-
private boolean isInValidSysUiState() {
return mIsSysUiStateValid;
}
@@ -457,59 +338,6 @@
}
}
- private void onDragCornerResize(MotionEvent ev) {
- int action = ev.getActionMasked();
- float x = ev.getX();
- float y = ev.getY() - mOhmOffset;
- if (action == MotionEvent.ACTION_DOWN) {
- mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
- if (mAllowGesture) {
- setCtrlType((int) x, (int) y);
- mDownPoint.set(x, y);
- mDownBounds.set(mPipBoundsState.getBounds());
- }
- } else if (mAllowGesture) {
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // We do not support multi touch for resizing via drag
- mAllowGesture = false;
- break;
- case MotionEvent.ACTION_MOVE:
- // Capture inputs
- if (!mThresholdCrossed
- && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
- mThresholdCrossed = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(x, y);
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
- false /* resize */);
- }
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
- mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
- mMinSize.y, mMaxSize, true,
- mDownBounds.width() > mDownBounds.height()));
- mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
- mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
- true /* useCurrentSize */);
- mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
- null);
- mPipBoundsState.setHasUserResizedPip(true);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- finishResize();
- break;
- }
- }
- }
-
private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
final int leftEdge = bounds.left;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 81705e2..11c356d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -212,7 +212,7 @@
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
- this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
+ this::updateMovementBounds, pipUiEventLogger,
menuController, mainExecutor);
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index a666e20..bfb60c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -253,7 +253,7 @@
: taskInfo.topActivityInfo;
params.layoutInDisplayCutoutMode = a.getInt(
R.styleable.Window_windowLayoutInDisplayCutoutMode,
- PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */)
+ PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
: params.layoutInDisplayCutoutMode);
params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 0000000..2ff4d90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
+natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index b3e8bd9..fa331af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -196,14 +196,18 @@
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
+ final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
final String handler = entry.getKey();
final int handlerId = entry.getValue();
- final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+
+ final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING);
os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
- os.end(token);
+ os.end(mappingEntryToken);
+
}
+ os.end(mappingsToken);
ctx.flush();
});
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 8207b85..07cd682 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -86,6 +86,8 @@
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
+
+ pipApp.tapPipToShowMenu(wmHelper)
}
}
@@ -194,6 +196,16 @@
}
}
+ @Postsubmit
+ @Test
+ fun menuOverlayMatchesTaskSurface() {
+ flicker.assertLayersEnd {
+ val pipAppRegion = visibleRegion(pipApp)
+ val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY)
+ pipAppRegion.coversExactly(pipMenuRegion.region)
+ }
+ }
+
/** {@inheritDoc} */
@FlakyTest(bugId = 267424412)
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 9719ba8..cc726cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -121,7 +121,7 @@
mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
mPipDismissTargetHandler,
- (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+ () -> {}, mPipUiEventLogger, mPhonePipMenuController,
mMainExecutor) {
@Override
public void pilferPointers() {
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 6ebfc63..ac75c07 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,9 +41,9 @@
#endif // __ANDROID__
}
-inline bool inter_character_justification() {
+inline bool letter_spacing_justification() {
#ifdef __ANDROID__
- return com_android_text_flags_inter_character_justification();
+ return com_android_text_flags_letter_spacing_justification();
#else
return true;
#endif // __ANDROID__
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5613369..d66d7f8 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,7 +72,7 @@
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
- const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
? paint->getRunFlag()
: minikin::RunFlag::NONE;
@@ -105,7 +105,7 @@
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
- const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
? paint->getRunFlag()
: minikin::RunFlag::NONE;
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 4fbe9ee..6ac9695 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -40,17 +40,6 @@
},
{
"file_patterns": [
- "[^/]*(Ringtone)[^/]*\\.java"
- ],
- "name": "MediaRingtoneTests",
- "options": [
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- },
- {
- "file_patterns": [
"[^/]*(LoudnessCodec)[^/]*\\.java"
],
"name": "LoudnessCodecApiTest",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 69708ec..8f3f82e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5166,13 +5166,12 @@
* dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
* the request was successful but the dispatch of focus change was delayed due to a fade
* operation.
- * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
- * other active {@link AudioFocusInfo} are {@code null}.
* @hide
*/
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @FocusRequestResult
public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
@NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
@Nullable FadeManagerConfiguration transientFadeMgrConfig) {
@@ -5514,6 +5513,26 @@
/**
* @hide
+ * @return All currently registered audio policy mixes.
+ */
+ @TestApi
+ @FlaggedApi(android.media.audiopolicy.Flags.FLAG_AUDIO_MIX_TEST_API)
+ @NonNull
+ public List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes() {
+ if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
+ return Collections.emptyList();
+ }
+
+ final IAudioService service = getService();
+ try {
+ return service.getRegisteredPolicyMixes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* @return true if an AudioPolicy was previously registered
*/
@TestApi
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f73be35f..293c561 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1984,6 +1984,9 @@
public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register);
/** @hide */
+ public static native int getRegisteredPolicyMixes(@NonNull List<AudioMix> devices);
+
+ /** @hide */
public static native int updatePolicyMixes(
AudioMix[] mixes,
AudioMixingRule[] updatedMixingRules);
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 40b0e3e..4f1a8ee 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -18,6 +18,7 @@
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -112,17 +113,11 @@
*/
public static final int FADE_STATE_ENABLED_DEFAULT = 1;
- /**
- * Defines the enabled state with Automotive specific configurations
- */
- public static final int FADE_STATE_ENABLED_AUTO = 2;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FADE_STATE", value = {
FADE_STATE_DISABLED,
FADE_STATE_ENABLED_DEFAULT,
- FADE_STATE_ENABLED_AUTO,
})
public @interface FadeStateEnum {}
@@ -143,7 +138,14 @@
* @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
* @see #getFadeInDurationForAudioAttributes(AudioAttributes)
*/
- public static final long DURATION_NOT_SET = 0;
+ public static final @DurationMillisLong long DURATION_NOT_SET = 0;
+
+ /** Defines the default fade out duration */
+ private static final @DurationMillisLong long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+
+ /** Defines the default fade in duration */
+ private static final @DurationMillisLong long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+
/** Map of Usage to Fade volume shaper configs wrapper */
private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap;
/** Map of AudioAttributes to Fade volume shaper configs wrapper */
@@ -161,14 +163,15 @@
/** fade state */
private final @FadeStateEnum int mFadeState;
/** fade out duration from builder - used for creating default fade out volume shaper */
- private final long mFadeOutDurationMillis;
+ private final @DurationMillisLong long mFadeOutDurationMillis;
/** fade in duration from builder - used for creating default fade in volume shaper */
- private final long mFadeInDurationMillis;
+ private final @DurationMillisLong long mFadeInDurationMillis;
/** delay after which the offending players are faded back in */
- private final long mFadeInDelayForOffendersMillis;
+ private final @DurationMillisLong long mFadeInDelayForOffendersMillis;
- private FadeManagerConfiguration(int fadeState, long fadeOutDurationMillis,
- long fadeInDurationMillis, long offendersFadeInDelayMillis,
+ private FadeManagerConfiguration(int fadeState, @DurationMillisLong long fadeOutDurationMillis,
+ @DurationMillisLong long fadeInDurationMillis,
+ @DurationMillisLong long offendersFadeInDelayMillis,
@NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap,
@NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap,
@NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes,
@@ -196,8 +199,6 @@
/**
* Get the fade state
- *
- * @return one of the {@link FadeStateEnum} state
*/
@FadeStateEnum
public int getFadeState() {
@@ -207,7 +208,7 @@
/**
* Get the list of usages that can be faded
*
- * @return list of {@link android.media.AudioAttributes.AttributeUsage} that shall be faded
+ * @return list of {@link android.media.AudioAttributes usages} that shall be faded
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
@@ -217,10 +218,10 @@
}
/**
- * Get the list of {@link android.media.AudioPlaybackConfiguration.PlayerType player types}
- * that cannot be faded
+ * Get the list of {@link android.media.AudioPlaybackConfiguration player types} that can be
+ * faded
*
- * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
+ * @return list of {@link android.media.AudioPlaybackConfiguration player types}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
@@ -230,10 +231,9 @@
}
/**
- * Get the list of {@link android.media.AudioAttributes.AttributeContentType content types}
- * that cannot be faded
+ * Get the list of {@link android.media.AudioAttributes content types} that can be faded
*
- * @return list of {@link android.media.AudioAttributes.AttributeContentType}
+ * @return list of {@link android.media.AudioAttributes content types}
* @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
@@ -267,15 +267,15 @@
}
/**
- * Get the duration used to fade out players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * Get the duration used to fade out players with {@link android.media.AudioAttributes usage}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usage the {@link android.media.AudioAttributes usage}
* @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
- public long getFadeOutDurationForUsage(int usage) {
+ @DurationMillisLong
+ public long getFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -283,15 +283,15 @@
}
/**
- * Get the duration used to fade in players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * Get the duration used to fade in players with {@link android.media.AudioAttributes usage}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usage the {@link android.media.AudioAttributes usage}
* @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
- public long getFadeInDurationForUsage(int usage) {
+ @DurationMillisLong
+ public long getFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -300,16 +300,17 @@
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usage the {@link android.media.AudioAttributes usage}
* @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
- * {@code null} otherwise
+ * {@code null} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
- public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int usage) {
+ public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(
+ @AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
@@ -318,16 +319,17 @@
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of player
+ * @param usage the {@link android.media.AudioAttributes usage}
* @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
- * {@code null} otherwise
+ * {@code null} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
- public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int usage) {
+ public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(
+ @AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
@@ -339,10 +341,11 @@
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return duration in milliseconds if set for the audio attributes or
- * {@link #DURATION_NOT_SET} otherwise
+ * {@link #DURATION_NOT_SET} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
+ @DurationMillisLong
public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
ensureFadingIsEnabled();
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -354,10 +357,11 @@
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return duration in milliseconds if set for the audio attributes or
- * {@link #DURATION_NOT_SET} otherwise
+ * {@link #DURATION_NOT_SET} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
+ @DurationMillisLong
public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
ensureFadingIsEnabled();
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -370,7 +374,7 @@
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or
- * {@code null} otherwise
+ * {@code null} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@@ -389,7 +393,7 @@
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the
- * audio attribute or {@code null} otherwise
+ * audio attribute or {@code null} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@@ -407,7 +411,7 @@
* configurations are defined
*
* @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or
- * empty list if none set.
+ * empty list if none set.
*/
@NonNull
public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() {
@@ -417,8 +421,14 @@
/**
* Get the delay after which the offending players are faded back in
*
+ * Players are categorized as offending if they do not honor audio focus state changes. For
+ * example - when an app loses audio focus, it is expected that the app stops any active
+ * player in favor of the app(s) that gained audio focus. However, if the app do not stop the
+ * audio playback, such players are termed as offenders.
+ *
* @return delay in milliseconds
*/
+ @DurationMillisLong
public long getFadeInDelayForOffenders() {
return mFadeInDelayForOffendersMillis;
}
@@ -435,8 +445,9 @@
/**
* Query if the usage is fadeable
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
- * @return {@code true} if usage is fadeable, {@code false} otherwise
+ * @param usage the {@link android.media.AudioAttributes usage}
+ * @return {@code true} if usage is fadeable, {@code false} when the fade state is set to
+ * {@link #FADE_STATE_DISABLED} or if the usage is not fadeable.
*/
public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) {
if (!isFadeEnabled()) {
@@ -448,9 +459,9 @@
/**
* Query if the content type is unfadeable
*
- * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+ * @param contentType the {@link android.media.AudioAttributes content type}
* @return {@code true} if content type is unfadeable or if fade state is set to
- * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+ * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) {
if (!isFadeEnabled()) {
@@ -462,11 +473,11 @@
/**
* Query if the player type is unfadeable
*
- * @param playerType the {@link android.media.AudioPlaybackConfiguration} player type
+ * @param playerType the {@link android.media.AudioPlaybackConfiguration player type}
* @return {@code true} if player type is unfadeable or if fade state is set to
- * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+ * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
- public boolean isPlayerTypeUnfadeable(int playerType) {
+ public boolean isPlayerTypeUnfadeable(@AudioPlaybackConfiguration.PlayerType int playerType) {
if (!isFadeEnabled()) {
return true;
}
@@ -478,7 +489,7 @@
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @return {@code true} if audio attributes is unfadeable or if fade state is set to
- * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+ * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
*/
public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) {
@@ -494,7 +505,7 @@
*
* @param uid the uid of application
* @return {@code true} if uid is unfadeable or if fade state is set to
- * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+ * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
public boolean isUidUnfadeable(int uid) {
if (!isFadeEnabled()) {
@@ -503,6 +514,20 @@
return mUnfadeableUids.contains(uid);
}
+ /**
+ * Returns the default fade out duration (in milliseconds)
+ */
+ public static @DurationMillisLong long getDefaultFadeOutDurationMillis() {
+ return DEFAULT_FADE_OUT_DURATION_MS;
+ }
+
+ /**
+ * Returns the default fade in duration (in milliseconds)
+ */
+ public static @DurationMillisLong long getDefaultFadeInDurationMillis() {
+ return DEFAULT_FADE_IN_DURATION_MS;
+ }
+
@Override
public String toString() {
return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState)
@@ -520,7 +545,7 @@
/**
* Convert fade state into a human-readable string
*
- * @param fadeState one of the fade state in {@link FadeStateEnum}
+ * @param fadeState one of {@link #FADE_STATE_DISABLED} or {@link #FADE_STATE_ENABLED_DEFAULT}
* @return human-readable string
* @hide
*/
@@ -531,8 +556,6 @@
return "FADE_STATE_DISABLED";
case FADE_STATE_ENABLED_DEFAULT:
return "FADE_STATE_ENABLED_DEFAULT";
- case FADE_STATE_ENABLED_AUTO:
- return "FADE_STATE_ENABLED_AUTO";
default:
return "unknown fade state: " + fadeState;
}
@@ -712,9 +735,9 @@
*
* <p><b>Notes:</b>
* <ul>
- * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
- * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
- * set/added. Failure to do so will result in an exception during {@link #build()}</li>
+ * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at
+ * least one valid usage to be set/added. Failure to do so will result in an exception
+ * during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
* config through {@link #setFadeOutDurationForUsage(int, long)} or
@@ -741,11 +764,6 @@
private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
- /** duration of the fade out curve */
- private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
- /** duration of the fade in curve */
- private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
-
/**
* delay after which a faded out player will be faded back in. This will be heard by the
* user only in the case of unmuting players that didn't respect audio focus and didn't
@@ -771,9 +789,10 @@
});
private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
- private long mFadeInDelayForOffendersMillis = DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
- private long mFadeOutDurationMillis;
- private long mFadeInDurationMillis;
+ private @DurationMillisLong long mFadeInDelayForOffendersMillis =
+ DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ private @DurationMillisLong long mFadeOutDurationMillis;
+ private @DurationMillisLong long mFadeInDurationMillis;
private long mBuilderFieldsSet;
private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
new SparseArray<>();
@@ -787,7 +806,8 @@
private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
/**
- * Constructs a new Builder with default fade out and fade in durations
+ * Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and
+ * {@link #DEFAULT_FADE_IN_DURATION_MS} durations.
*/
public Builder() {
mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
@@ -800,7 +820,8 @@
* @param fadeOutDurationMillis duration in milliseconds used for fading out
* @param fadeInDurationMills duration in milliseconds used for fading in
*/
- public Builder(long fadeOutDurationMillis, long fadeInDurationMills) {
+ public Builder(@DurationMillisLong long fadeOutDurationMillis,
+ @DurationMillisLong long fadeInDurationMills) {
mFadeOutDurationMillis = fadeOutDurationMillis;
mFadeInDurationMillis = fadeInDurationMills;
}
@@ -830,7 +851,8 @@
/**
* Set the overall fade state
*
- * @param state one of the {@link FadeStateEnum} states
+ * @param state one of the {@link #FADE_STATE_DISABLED} or
+ * {@link #FADE_STATE_ENABLED_DEFAULT} states
* @return the same Builder instance
* @throws IllegalArgumentException if the fade state is invalid
* @see #getFadeState()
@@ -844,21 +866,22 @@
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
* <p>
* This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+ * @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
- * to fade out players with usage
+ * to fade out players with usage
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeOutVolumeShaperConfigForUsage(int)
*/
@NonNull
- public Builder setFadeOutVolumeShaperConfigForUsage(int usage,
+ public Builder setFadeOutVolumeShaperConfigForUsage(
+ @AudioAttributes.AttributeUsage int usage,
@Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
validateUsage(usage);
getFadeVolShaperConfigWrapperForUsage(usage)
@@ -869,21 +892,22 @@
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
* <p>
* This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usage the {@link android.media.AudioAttributes usage}
* @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
- * to fade in players with usage
+ * to fade in players with usage
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeInVolumeShaperConfigForUsage(int)
*/
@NonNull
- public Builder setFadeInVolumeShaperConfigForUsage(int usage,
+ public Builder setFadeInVolumeShaperConfigForUsage(
+ @AudioAttributes.AttributeUsage int usage,
@Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
validateUsage(usage);
getFadeVolShaperConfigWrapperForUsage(usage)
@@ -894,7 +918,7 @@
/**
* Set the duration used for fading out players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade out players with given usage.
@@ -904,17 +928,18 @@
* {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
* {@code null}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+ * @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeOutDurationMillis positive duration in milliseconds or
- * {@link #DURATION_NOT_SET}
+ * {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade out duration is non-positive with the
- * exception of {@link #DURATION_NOT_SET}
+ * exception of {@link #DURATION_NOT_SET}
* @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
* @see #getFadeOutDurationForUsage(int)
*/
@NonNull
- public Builder setFadeOutDurationForUsage(int usage, long fadeOutDurationMillis) {
+ public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage,
+ @DurationMillisLong long fadeOutDurationMillis) {
validateUsage(usage);
VolumeShaper.Configuration fadeOutVShaperConfig =
createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
@@ -924,7 +949,7 @@
/**
* Set the duration used for fading in players with
- * {@link android.media.AudioAttributes.AttributeUsage}
+ * {@link android.media.AudioAttributes usage}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade in players with given usage.
@@ -934,17 +959,18 @@
* {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
* {@code null}
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+ * @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeInDurationMillis positive duration in milliseconds or
- * {@link #DURATION_NOT_SET}
+ * {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade in duration is non-positive with the
- * exception of {@link #DURATION_NOT_SET}
+ * exception of {@link #DURATION_NOT_SET}
* @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
* @see #getFadeInDurationForUsage(int)
*/
@NonNull
- public Builder setFadeInDurationForUsage(int usage, long fadeInDurationMillis) {
+ public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage,
+ @DurationMillisLong long fadeInDurationMillis) {
validateUsage(usage);
VolumeShaper.Configuration fadeInVShaperConfig =
createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
@@ -962,9 +988,8 @@
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
- * fade out players with audio attribute
+ * fade out players with audio attribute
* @return the same Builder instance
- * @throws NullPointerException if the audio attributes is {@code null}
* @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
*/
@NonNull
@@ -988,7 +1013,7 @@
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
- * fade in players with audio attribute
+ * fade in players with audio attribute
* @return the same Builder instance
* @throws NullPointerException if the audio attributes is {@code null}
* @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
@@ -1017,12 +1042,12 @@
* {@code null}
*
* @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
- * duration will be set/updated/reset
+ * duration will be set/updated/reset
* @param fadeOutDurationMillis positive duration in milliseconds or
- * {@link #DURATION_NOT_SET}
+ * {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade out duration is non-positive with the
- * exception of {@link #DURATION_NOT_SET}
+ * exception of {@link #DURATION_NOT_SET}
* @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
* @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)
@@ -1030,7 +1055,7 @@
@NonNull
public Builder setFadeOutDurationForAudioAttributes(
@NonNull AudioAttributes audioAttributes,
- long fadeOutDurationMillis) {
+ @DurationMillisLong long fadeOutDurationMillis) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
VolumeShaper.Configuration fadeOutVShaperConfig =
createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
@@ -1039,8 +1064,7 @@
}
/**
- * Set the duration used for fading in players of type
- * {@link android.media.AudioAttributes}.
+ * Set the duration used for fading in players of type {@link android.media.AudioAttributes}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade in players with given usage.
@@ -1051,19 +1075,19 @@
* {@code null}
*
* @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
- * duration will be set/updated/reset
+ * duration will be set/updated/reset
* @param fadeInDurationMillis positive duration in milliseconds or
- * {@link #DURATION_NOT_SET}
+ * {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade in duration is non-positive with the
- * exception of {@link #DURATION_NOT_SET}
+ * exception of {@link #DURATION_NOT_SET}
* @see #getFadeInDurationForAudioAttributes(AudioAttributes)
* @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)
*/
@NonNull
public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
- long fadeInDurationMillis) {
+ @DurationMillisLong long fadeInDurationMillis) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
VolumeShaper.Configuration fadeInVShaperConfig =
createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
@@ -1072,22 +1096,18 @@
}
/**
- * Set the list of {@link android.media.AudioAttributes.AttributeUsage} that can be faded
+ * Set the list of {@link android.media.AudioAttributes usage} that can be faded
*
* <p>This is a positive list. Players with matching usage will be considered for fading.
* Usages that are not part of this list will not be faded
*
- * <p>Passing an empty list as input clears the existing list. This can be used to
- * reset the list when using a copy constructor
- *
* <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
* usage to be set/added. Failure to do so will result in an exception during
* {@link #build()}
*
- * @param usages List of the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usages List of the {@link android.media.AudioAttributes usages}
* @return the same Builder instance
* @throws IllegalArgumentException if the usages are invalid
- * @throws NullPointerException if the usage list is {@code null}
* @see #getFadeableUsages()
*/
@NonNull
@@ -1101,9 +1121,9 @@
}
/**
- * Add the {@link android.media.AudioAttributes.AttributeUsage} to the fadeable list
+ * Add the {@link android.media.AudioAttributes usage} to the fadeable list
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @param usage the {@link android.media.AudioAttributes usage}
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeableUsages()
@@ -1120,30 +1140,23 @@
}
/**
- * Remove the {@link android.media.AudioAttributes.AttributeUsage} from the fadeable list
- * <p>
- * Players of this usage type will not be faded.
+ * Clears the fadeable {@link android.media.AudioAttributes usage} list
*
- * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * <p>This can be used to reset the list when using a copy constructor
+ *
* @return the same Builder instance
- * @throws IllegalArgumentException if the usage is invalid
* @see #getFadeableUsages()
* @see #setFadeableUsages(List)
*/
@NonNull
- public Builder clearFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
- validateUsage(usage);
+ public Builder clearFadeableUsages() {
setFlag(IS_FADEABLE_USAGES_FIELD_SET);
- int index = mFadeableUsages.indexOf(usage);
- if (index != INVALID_INDEX) {
- mFadeableUsages.remove(index);
- }
+ mFadeableUsages.clear();
return this;
}
/**
- * Set the list of {@link android.media.AudioAttributes.AttributeContentType} that can not
- * be faded
+ * Set the list of {@link android.media.AudioAttributes content type} that can not be faded
*
* <p>This is a negative list. Players with matching content type of this list will not be
* faded. Content types that are not part of this list will be considered for fading.
@@ -1151,10 +1164,9 @@
* <p>Passing an empty list as input clears the existing list. This can be used to
* reset the list when using a copy constructor
*
- * @param contentTypes list of {@link android.media.AudioAttributes.AttributeContentType}
+ * @param contentTypes list of {@link android.media.AudioAttributes content types}
* @return the same Builder instance
* @throws IllegalArgumentException if the content types are invalid
- * @throws NullPointerException if the content type list is {@code null}
* @see #getUnfadeableContentTypes()
*/
@NonNull
@@ -1168,9 +1180,9 @@
}
/**
- * Add the {@link android.media.AudioAttributes.AttributeContentType} to unfadeable list
+ * Add the {@link android.media.AudioAttributes content type} to unfadeable list
*
- * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+ * @param contentType the {@link android.media.AudioAttributes content type}
* @return the same Builder instance
* @throws IllegalArgumentException if the content type is invalid
* @see #setUnfadeableContentTypes(List)
@@ -1188,24 +1200,18 @@
}
/**
- * Remove the {@link android.media.AudioAttributes.AttributeContentType} from the
- * unfadeable list
+ * Clears the unfadeable {@link android.media.AudioAttributes content type} list
*
- * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+ * <p>This can be used to reset the list when using a copy constructor
+ *
* @return the same Builder instance
- * @throws IllegalArgumentException if the content type is invalid
* @see #setUnfadeableContentTypes(List)
* @see #getUnfadeableContentTypes()
*/
@NonNull
- public Builder clearUnfadeableContentType(
- @AudioAttributes.AttributeContentType int contentType) {
- validateContentType(contentType);
+ public Builder clearUnfadeableContentTypes() {
setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
- int index = mUnfadeableContentTypes.indexOf(contentType);
- if (index != INVALID_INDEX) {
- mUnfadeableContentTypes.remove(index);
- }
+ mUnfadeableContentTypes.clear();
return this;
}
@@ -1213,14 +1219,10 @@
* Set the uids that cannot be faded
*
* <p>This is a negative list. Players with matching uid of this list will not be faded.
- * Uids that are not part of this list shall be considered for fading
- *
- * <p>Passing an empty list as input clears the existing list. This can be used to
- * reset the list when using a copy constructor
+ * Uids that are not part of this list shall be considered for fading.
*
* @param uids list of uids
* @return the same Builder instance
- * @throws NullPointerException if the uid list is {@code null}
* @see #getUnfadeableUids()
*/
@NonNull
@@ -1248,19 +1250,17 @@
}
/**
- * Remove the uid from unfadeable list
+ * Clears the unfadeable uid list
*
- * @param uid client uid
+ * <p>This can be used to reset the list when using a copy constructor.
+ *
* @return the same Builder instance
* @see #setUnfadeableUids(List)
* @see #getUnfadeableUids()
*/
@NonNull
- public Builder clearUnfadeableUid(int uid) {
- int index = mUnfadeableUids.indexOf(uid);
- if (index != INVALID_INDEX) {
- mUnfadeableUids.remove(index);
- }
+ public Builder clearUnfadeableUids() {
+ mUnfadeableUids.clear();
return this;
}
@@ -1270,24 +1270,19 @@
* <p>This is a negative list. Players with matching audio attributes of this list will not
* be faded. Audio attributes that are not part of this list shall be considered for fading.
*
- * <p>Passing an empty list as input clears any existing list. This can be used to
- * reset the list when using a copy constructor
- *
* <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
- * negatively impact fadeability decision if such an audio attribute and corresponding
- * usage fall into opposing lists.
+ * negatively impact fadeability decision (if such an audio attribute and corresponding
+ * usage fall into opposing lists).
* For example:
* <pre class=prettyprint>
* AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
* is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
- * It is an undefined behavior to have an
- * {@link android.media.AudioAttributes.AttributeUsage} in the fadeable usage list and the
- * corresponding generic {@link android.media.AudioAttributes} in the unfadeable list. Such
- * cases will result in an exception during {@link #build()}
+ * It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the
+ * fadeable usage list and the corresponding generic {@link android.media.AudioAttributes}
+ * in the unfadeable list. Such cases will result in an exception during {@link #build()}.
*
* @param attrs list of {@link android.media.AudioAttributes}
* @return the same Builder instance
- * @throws NullPointerException if the audio attributes list is {@code null}
* @see #getUnfadeableAudioAttributes()
*/
@NonNull
@@ -1303,7 +1298,6 @@
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @return the same Builder instance
- * @throws NullPointerException if the audio attributes is {@code null}
* @see #setUnfadeableAudioAttributes(List)
* @see #getUnfadeableAudioAttributes()
*/
@@ -1317,19 +1311,16 @@
}
/**
- * Remove the {@link android.media.AudioAttributes} from the unfadeable list.
+ * Clears the unfadeable {@link android.media.AudioAttributes} list.
*
- * @param audioAttributes the {@link android.media.AudioAttributes}
+ * <p>This can be used to reset the list when using a copy constructor.
+ *
* @return the same Builder instance
- * @throws NullPointerException if the audio attributes is {@code null}
* @see #getUnfadeableAudioAttributes()
*/
@NonNull
- public Builder clearUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
- Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
- if (mUnfadeableAudioAttributes.contains(audioAttributes)) {
- mUnfadeableAudioAttributes.remove(audioAttributes);
- }
+ public Builder clearUnfadeableAudioAttributes() {
+ mUnfadeableAudioAttributes.clear();
return this;
}
@@ -1345,7 +1336,7 @@
* @see #getFadeInDelayForOffenders()
*/
@NonNull
- public Builder setFadeInDelayForOffenders(long delayMillis) {
+ public Builder setFadeInDelayForOffenders(@DurationMillisLong long delayMillis) {
Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
mFadeInDelayForOffendersMillis = delayMillis;
return this;
@@ -1469,7 +1460,6 @@
switch(state) {
case FADE_STATE_DISABLED:
case FADE_STATE_ENABLED_DEFAULT:
- case FADE_STATE_ENABLED_AUTO:
break;
default:
throw new IllegalArgumentException("Unknown fade state: " + state);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8dfa6be..98bd3ca 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -365,6 +365,8 @@
oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
+ List<AudioMix> getRegisteredPolicyMixes();
+
void unregisterAudioPolicy(in IAudioPolicyCallback pcb);
int addMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb);
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 1e57be2..c96a400 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -21,7 +21,6 @@
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
-import android.os.VibrationEffect;
/**
* @hide
@@ -30,23 +29,12 @@
/** Used for Ringtone.java playback */
@UnsupportedAppUsage
oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping);
+ oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa,
+ float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
oneway void stop(IBinder token);
boolean isPlaying(IBinder token);
-
- // RingtoneV1
- oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa,
- float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
- boolean hapticGeneratorEnabled);
-
- // RingtoneV2
- oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa,
- boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve,
- float volume, boolean looping, boolean hapticGeneratorEnabled,
- in @nullable VolumeShaper.Configuration volumeShaperConfig);
- oneway void setLooping(IBinder token, boolean looping);
- oneway void setVolume(IBinder token, float volume);
- oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled);
+ boolean hapticGeneratorEnabled);
/** Used for Notification sound playback. */
oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume);
diff --git a/media/java/android/media/LocalRingtonePlayer.java b/media/java/android/media/LocalRingtonePlayer.java
deleted file mode 100644
index fe7cc3e..0000000
--- a/media/java/android/media/LocalRingtonePlayer.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * Plays a ringtone on the local process.
- * @hide
- */
-public class LocalRingtonePlayer
- implements RingtoneV2.RingtonePlayer, MediaPlayer.OnCompletionListener {
- private static final String TAG = "LocalRingtonePlayer";
-
- // keep references on active Ringtones until stopped or completion listener called.
- private static final ArrayList<LocalRingtonePlayer> sActiveMediaPlayers = new ArrayList<>();
-
- private final MediaPlayer mMediaPlayer;
- private final AudioAttributes mAudioAttributes;
- private final RingtoneV2.RingtonePlayer mVibrationPlayer;
- private final Ringtone.Injectables mInjectables;
- private final AudioManager mAudioManager;
- private final VolumeShaper mVolumeShaper;
- private HapticGenerator mHapticGenerator;
-
- private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer,
- @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables,
- @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator,
- @Nullable VolumeShaper volumeShaper,
- @Nullable RingtoneV2.RingtonePlayer vibrationPlayer) {
- Objects.requireNonNull(mediaPlayer);
- Objects.requireNonNull(audioAttributes);
- Objects.requireNonNull(injectables);
- Objects.requireNonNull(audioManager);
- mMediaPlayer = mediaPlayer;
- mAudioAttributes = audioAttributes;
- mInjectables = injectables;
- mAudioManager = audioManager;
- mVolumeShaper = volumeShaper;
- mVibrationPlayer = vibrationPlayer;
- mHapticGenerator = hapticGenerator;
- }
-
- /**
- * Creates a {@link LocalRingtonePlayer} for a Uri, returning null if the Uri can't be
- * loaded in the local player.
- */
- @Nullable
- static RingtoneV2.RingtonePlayer create(@NonNull Context context,
- @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
- @NonNull Uri soundUri,
- @NonNull AudioAttributes audioAttributes,
- boolean isVibrationOnly,
- @Nullable VibrationEffect vibrationEffect,
- @NonNull Ringtone.Injectables injectables,
- @Nullable VolumeShaper.Configuration volumeShaperConfig,
- @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled,
- boolean initialLooping, float initialVolume) {
- Objects.requireNonNull(context);
- Objects.requireNonNull(soundUri);
- Objects.requireNonNull(audioAttributes);
- Trace.beginSection("createLocalMediaPlayer");
- MediaPlayer mediaPlayer = injectables.newMediaPlayer();
- HapticGenerator hapticGenerator = null;
- try {
- mediaPlayer.setDataSource(context, soundUri);
- mediaPlayer.setAudioAttributes(audioAttributes);
- mediaPlayer.setPreferredDevice(preferredDevice);
- mediaPlayer.setLooping(initialLooping);
- mediaPlayer.setVolume(isVibrationOnly ? 0 : initialVolume);
- if (initialHapticGeneratorEnabled) {
- hapticGenerator = injectables.createHapticGenerator(mediaPlayer);
- if (hapticGenerator != null) {
- // In practise, this should always be non-null because the initial value is
- // not true unless it's available.
- hapticGenerator.setEnabled(true);
- vibrationEffect = null; // Don't play the VibrationEffect.
- }
- }
- VolumeShaper volumeShaper = null;
- if (volumeShaperConfig != null) {
- volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
- }
- mediaPlayer.prepare();
- if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
- if (injectables.hasHapticChannels(mediaPlayer)) {
- // Don't play the Vibration effect if the URI has haptic channels.
- vibrationEffect = null;
- }
- }
- VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
- new VibrationEffectPlayer(
- vibrationEffect, audioAttributes, vibrator, initialLooping);
- if (isVibrationOnly && vibrationEffectPlayer != null) {
- // Abandon the media player now that it's confirmed to not have haptic channels.
- mediaPlayer.release();
- return vibrationEffectPlayer;
- }
- return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
- hapticGenerator, volumeShaper, vibrationEffectPlayer);
- } catch (SecurityException | IOException e) {
- if (hapticGenerator != null) {
- hapticGenerator.release();
- }
- // volume shaper closes with media player
- mediaPlayer.release();
- return null;
- } finally {
- Trace.endSection();
- }
- }
-
- /**
- * Creates a {@link LocalRingtonePlayer} for an externally referenced file descriptor. This is
- * intended for loading a fallback from an internal resource, rather than via a Uri.
- */
- @Nullable
- static LocalRingtonePlayer createForFallback(
- @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
- @NonNull AssetFileDescriptor afd,
- @NonNull AudioAttributes audioAttributes,
- @Nullable VibrationEffect vibrationEffect,
- @NonNull Ringtone.Injectables injectables,
- @Nullable VolumeShaper.Configuration volumeShaperConfig,
- @Nullable AudioDeviceInfo preferredDevice,
- boolean initialLooping, float initialVolume) {
- // Haptic generator not supported for fallback.
- Objects.requireNonNull(audioManager);
- Objects.requireNonNull(afd);
- Objects.requireNonNull(audioAttributes);
- Trace.beginSection("createFallbackLocalMediaPlayer");
-
- MediaPlayer mediaPlayer = injectables.newMediaPlayer();
- try {
- if (afd.getDeclaredLength() < 0) {
- mediaPlayer.setDataSource(afd.getFileDescriptor());
- } else {
- mediaPlayer.setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- mediaPlayer.setAudioAttributes(audioAttributes);
- mediaPlayer.setPreferredDevice(preferredDevice);
- mediaPlayer.setLooping(initialLooping);
- mediaPlayer.setVolume(initialVolume);
- VolumeShaper volumeShaper = null;
- if (volumeShaperConfig != null) {
- volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
- }
- mediaPlayer.prepare();
- if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
- if (injectables.hasHapticChannels(mediaPlayer)) {
- // Don't play the Vibration effect if the URI has haptic channels.
- vibrationEffect = null;
- }
- }
- VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
- new VibrationEffectPlayer(
- vibrationEffect, audioAttributes, vibrator, initialLooping);
- return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
- /* hapticGenerator= */ null, volumeShaper, vibrationEffectPlayer);
- } catch (SecurityException | IOException e) {
- Log.e(TAG, "Failed to open fallback ringtone");
- // TODO: vibration-effect-only / no-sound LocalRingtonePlayer.
- mediaPlayer.release();
- return null;
- } finally {
- Trace.endSection();
- }
- }
-
- @Override
- public boolean play() {
- // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
- // (typically because ringer mode is vibrate).
- if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
- == 0 && (mAudioAttributes.areHapticChannelsMuted() || !hasHapticChannels())) {
- maybeStartVibration();
- return true; // Successfully played while muted.
- }
- synchronized (sActiveMediaPlayers) {
- // We keep-alive when a mediaplayer is active, since its finalizer would stop the
- // ringtone. This isn't necessary for vibrations in the vibrator service
- // (i.e. maybeStartVibration in the muted case, above).
- sActiveMediaPlayers.add(this);
- }
-
- mMediaPlayer.setOnCompletionListener(this);
- mMediaPlayer.start();
- if (mVolumeShaper != null) {
- mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
- }
- maybeStartVibration();
- return true;
- }
-
- private void maybeStartVibration() {
- if (mVibrationPlayer != null) {
- mVibrationPlayer.play();
- }
- }
-
- @Override
- public boolean isPlaying() {
- return mMediaPlayer.isPlaying();
- }
-
- @Override
- public void stopAndRelease() {
- synchronized (sActiveMediaPlayers) {
- sActiveMediaPlayers.remove(this);
- }
- try {
- mMediaPlayer.stop();
- } finally {
- if (mVibrationPlayer != null) {
- try {
- mVibrationPlayer.stopAndRelease();
- } catch (Exception e) {
- Log.e(TAG, "Exception stopping ringtone vibration", e);
- }
- }
- if (mHapticGenerator != null) {
- mHapticGenerator.release();
- }
- mMediaPlayer.setOnCompletionListener(null);
- mMediaPlayer.reset();
- mMediaPlayer.release();
- }
- }
-
- @Override
- public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
- mMediaPlayer.setPreferredDevice(audioDeviceInfo);
- }
-
- @Override
- public void setLooping(boolean looping) {
- boolean wasLooping = mMediaPlayer.isLooping();
- if (wasLooping == looping) {
- return;
- }
- mMediaPlayer.setLooping(looping);
- if (mVibrationPlayer != null) {
- mVibrationPlayer.setLooping(looping);
- }
- }
-
- @Override
- public void setHapticGeneratorEnabled(boolean enabled) {
- if (mVibrationPlayer != null) {
- // Ignore haptic generator changes if a vibration player is present. The decision to
- // use one or the other happens before this object is constructed.
- return;
- }
- if (enabled && mHapticGenerator == null && !hasHapticChannels()) {
- mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer);
- }
- if (mHapticGenerator != null) {
- mHapticGenerator.setEnabled(enabled);
- }
- }
-
- @Override
- public void setVolume(float volume) {
- mMediaPlayer.setVolume(volume);
- // no effect on vibration player
- }
-
- @Override
- public boolean hasHapticChannels() {
- return mInjectables.hasHapticChannels(mMediaPlayer);
- }
-
- @Override
- public void onCompletion(MediaPlayer mp) {
- synchronized (sActiveMediaPlayers) {
- sActiveMediaPlayers.remove(this);
- }
- mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
- // No effect on vibration: either it's looping and this callback only happens when stopped,
- // or it's not looping, in which case the vibration should play to its own completion.
- }
-
- /** A RingtonePlayer that only plays a VibrationEffect. */
- static class VibrationEffectPlayer implements RingtoneV2.RingtonePlayer {
- private static final int VIBRATION_LOOP_DELAY_MS = 200;
- private final VibrationEffect mVibrationEffect;
- private final VibrationAttributes mVibrationAttributes;
- private final Vibrator mVibrator;
- private boolean mIsLooping;
- private boolean mStartedVibration;
-
- VibrationEffectPlayer(@NonNull VibrationEffect vibrationEffect,
- @NonNull AudioAttributes audioAttributes,
- @NonNull Vibrator vibrator, boolean initialLooping) {
- mVibrationEffect = vibrationEffect;
- mVibrationAttributes = new VibrationAttributes.Builder(audioAttributes).build();
- mVibrator = vibrator;
- mIsLooping = initialLooping;
- }
-
- @Override
- public boolean play() {
- if (!mStartedVibration) {
- try {
- // Adjust the vibration effect to loop.
- VibrationEffect loopAdjustedEffect =
- mVibrationEffect.applyRepeatingIndefinitely(
- mIsLooping, VIBRATION_LOOP_DELAY_MS);
- mVibrator.vibrate(loopAdjustedEffect, mVibrationAttributes);
- mStartedVibration = true;
- } catch (Exception e) {
- // Catch exceptions widely, because we don't want to "leak" looping sounds or
- // vibrations if something goes wrong.
- Log.e(TAG, "Problem starting " + (mIsLooping ? "looping " : "") + "vibration "
- + "for ringtone: " + mVibrationEffect, e);
- return false;
- }
- }
- return true;
- }
-
- @Override
- public boolean isPlaying() {
- return mStartedVibration;
- }
-
- @Override
- public void stopAndRelease() {
- if (mStartedVibration) {
- try {
- mVibrator.cancel(mVibrationAttributes.getUsage());
- mStartedVibration = false;
- } catch (Exception e) {
- // Catch exceptions widely, because we don't want to "leak" looping sounds or
- // vibrations if something goes wrong.
- Log.e(TAG, "Problem stopping vibration for ringtone", e);
- }
- }
- }
-
- @Override
- public void setPreferredDevice(AudioDeviceInfo audioDeviceInfo) {
- // no-op
- }
-
- @Override
- public void setLooping(boolean looping) {
- if (looping == mIsLooping) {
- return;
- }
- mIsLooping = looping;
- if (mStartedVibration) {
- if (!mIsLooping) {
- // Was looping, stop looping
- stopAndRelease();
- }
- // Else was not looping, but can't interfere with a running vibration without
- // restarting it, and don't know if it was finished. So do nothing: apps shouldn't
- // toggle looping after calling play anyway.
- }
- }
-
- @Override
- public void setHapticGeneratorEnabled(boolean enabled) {
- // n/a
- }
-
- @Override
- public void setVolume(float volume) {
- // n/a
- }
-
- @Override
- public boolean hasHapticChannels() {
- return false;
- }
- }
-}
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index 058c5be..49890c1 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -11,6 +11,3 @@
per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=set noparent
per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS
-
-# Haptics team also works on Ringtone
-per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 8800dc8..e78dc31 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -16,31 +16,27 @@
package android.media;
-import android.Manifest;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.media.audiofx.HapticGenerator;
import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
import android.os.RemoteException;
import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.util.Log;
-
import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.io.IOException;
+import java.util.ArrayList;
/**
* Ringtone provides a quick method for playing a ringtone, notification, or
@@ -53,39 +49,7 @@
*/
public class Ringtone {
private static final String TAG = "Ringtone";
-
- /**
- * The ringtone should only play sound. Any vibration is managed externally.
- * @hide
- */
- public static final int MEDIA_SOUND = 1;
- /**
- * The ringtone should only play vibration. Any sound is managed externally.
- * Requires the {@link android.Manifest.permission#VIBRATE} permission.
- * @hide
- */
- public static final int MEDIA_VIBRATION = 1 << 1;
- /**
- * The ringtone should play sound and vibration.
- * @hide
- */
- public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION;
-
- // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be
- // safe if new media types were added.
- static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION;
-
- /**
- * Declares the types of media that this Ringtone is allowed to play.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "MEDIA_", value = {
- MEDIA_SOUND,
- MEDIA_VIBRATION,
- MEDIA_SOUND_AND_VIBRATION,
- })
- public @interface RingtoneMedia {}
+ private static final boolean LOGD = true;
private static final String[] MEDIA_COLUMNS = new String[] {
MediaStore.Audio.Media._ID,
@@ -95,70 +59,51 @@
private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
+ MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
- // Flag-selected ringtone implementation to use.
- private final ApiInterface mApiImpl;
+ // keep references on active Ringtones until stopped or completion listener called.
+ private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>();
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+ private VolumeShaper.Configuration mVolumeShaperConfig;
+ private VolumeShaper mVolumeShaper;
+
+ /**
+ * Flag indicating if we're allowed to fall back to remote playback using
+ * {@link #mRemotePlayer}. Typically this is false when we're the remote
+ * player and there is nobody else to delegate to.
+ */
+ private final boolean mAllowRemote;
+ private final IRingtonePlayer mRemotePlayer;
+ private final Binder mRemoteToken;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private MediaPlayer mLocalPlayer;
+ private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+ private HapticGenerator mHapticGenerator;
+
+ @UnsupportedAppUsage
+ private Uri mUri;
+ private String mTitle;
+
+ private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+ private boolean mPreferBuiltinDevice;
+ // playback properties, use synchronized with mPlaybackSettingsLock
+ private boolean mIsLooping = false;
+ private float mVolume = 1.0f;
+ private boolean mHapticGeneratorEnabled = false;
+ private final Object mPlaybackSettingsLock = new Object();
/** {@hide} */
@UnsupportedAppUsage
public Ringtone(Context context, boolean allowRemote) {
- mApiImpl = new RingtoneV1(context, allowRemote);
- }
-
- /**
- * Constructor for legacy V1 initialization paths using non-public APIs on RingtoneV1.
- */
- private Ringtone(RingtoneV1 ringtoneV1) {
- mApiImpl = ringtoneV1;
- }
-
- private Ringtone(Builder builder, @Ringtone.RingtoneMedia int effectiveEnabledMedia,
- @NonNull AudioAttributes effectiveAudioAttributes,
- @Nullable VibrationEffect effectiveVibrationEffect,
- boolean effectiveHapticGeneratorEnabled) {
- mApiImpl = new RingtoneV2(builder.mContext, builder.mInjectables, builder.mAllowRemote,
- effectiveEnabledMedia, builder.mUri, effectiveAudioAttributes,
- builder.mUseExactAudioAttributes, builder.mVolumeShaperConfig,
- builder.mPreferBuiltinDevice, builder.mInitialSoundVolume, builder.mLooping,
- effectiveHapticGeneratorEnabled, effectiveVibrationEffect);
- }
-
- /**
- * Temporary V1 constructor for legacy V1 paths with audio attributes.
- * @hide
- */
- public static Ringtone createV1WithCustomAudioAttributes(
- Context context, AudioAttributes audioAttributes, Uri uri,
- VolumeShaper.Configuration volumeShaperConfig, boolean allowRemote) {
- RingtoneV1 ringtoneV1 = new RingtoneV1(context, allowRemote);
- ringtoneV1.setAudioAttributesField(audioAttributes);
- ringtoneV1.setUri(uri, volumeShaperConfig);
- ringtoneV1.reinitializeActivePlayer();
- return new Ringtone(ringtoneV1);
- }
-
- /**
- * Temporary V1 constructor for legacy V1 paths with stream type.
- * @hide
- */
- public static Ringtone createV1WithCustomStreamType(
- Context context, int streamType, Uri uri,
- VolumeShaper.Configuration volumeShaperConfig) {
- RingtoneV1 ringtoneV1 = new RingtoneV1(context, /* allowRemote= */ true);
- if (streamType >= 0) {
- ringtoneV1.setStreamType(streamType);
- }
- ringtoneV1.setUri(uri, volumeShaperConfig);
- if (!ringtoneV1.reinitializeActivePlayer()) {
- Log.e(TAG, "Failed to open ringtone " + uri);
- return null;
- }
- return new Ringtone(ringtoneV1);
- }
-
- /** @hide */
- @RingtoneMedia
- public int getEnabledMedia() {
- return mApiImpl.getEnabledMedia();
+ mContext = context;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mAllowRemote = allowRemote;
+ mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
+ mRemoteToken = allowRemote ? new Binder() : null;
}
/**
@@ -169,7 +114,10 @@
*/
@Deprecated
public void setStreamType(int streamType) {
- mApiImpl.setStreamType(streamType);
+ PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()");
+ setAudioAttributes(new AudioAttributes.Builder()
+ .setInternalLegacyStreamType(streamType)
+ .build());
}
/**
@@ -181,7 +129,7 @@
*/
@Deprecated
public int getStreamType() {
- return mApiImpl.getStreamType();
+ return AudioAttributes.toLegacyStreamType(mAudioAttributes);
}
/**
@@ -190,45 +138,54 @@
*/
public void setAudioAttributes(AudioAttributes attributes)
throws IllegalArgumentException {
- mApiImpl.setAudioAttributes(attributes);
+ setAudioAttributesField(attributes);
+ // The audio attributes have to be set before the media player is prepared.
+ // Re-initialize it.
+ setUri(mUri, mVolumeShaperConfig);
+ createLocalMediaPlayer();
}
/**
- * Returns the vibration effect that this ringtone was created with, if vibration is enabled.
- * Otherwise, returns null.
+ * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create
+ * the media player.
* @hide
*/
- @Nullable
- public VibrationEffect getVibrationEffect() {
- return mApiImpl.getVibrationEffect();
- }
-
- /** @hide */
- @VisibleForTesting
- public boolean getPreferBuiltinDevice() {
- return mApiImpl.getPreferBuiltinDevice();
- }
-
- /** @hide */
- @VisibleForTesting
- public VolumeShaper.Configuration getVolumeShaperConfig() {
- return mApiImpl.getVolumeShaperConfig();
+ public void setAudioAttributesField(@Nullable AudioAttributes attributes) {
+ if (attributes == null) {
+ throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
+ }
+ mAudioAttributes = attributes;
}
/**
- * Returns whether this player is local only, or can defer to the remote player. The
- * result may differ from the builder if there is no remote player available at all.
- * @hide
+ * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+ * the one on which outgoing audio for SIM calls is played.
+ *
+ * @param audioManager the audio manage.
+ * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+ * none can be found.
*/
- @VisibleForTesting
- public boolean isLocalOnly() {
- return mApiImpl.isLocalOnly();
+ private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+ AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ return device;
+ }
+ }
+ return null;
}
- /** @hide */
- @VisibleForTesting
- public boolean isUsingRemotePlayer() {
- return mApiImpl.isUsingRemotePlayer();
+ /**
+ * Sets the preferred device of the ringtong playback to the built-in device.
+ *
+ * @hide
+ */
+ public boolean preferBuiltinDevice(boolean enable) {
+ mPreferBuiltinDevice = enable;
+ if (mLocalPlayer == null) {
+ return true;
+ }
+ return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
}
/**
@@ -237,16 +194,76 @@
* false if it did not succeed and can't be tried remotely.
* @hide
*/
- public boolean reinitializeActivePlayer() {
- return mApiImpl.reinitializeActivePlayer();
+ public boolean createLocalMediaPlayer() {
+ Trace.beginSection("createLocalMediaPlayer");
+ if (mUri == null) {
+ Log.e(TAG, "Could not create media player as no URI was provided.");
+ return mAllowRemote && mRemotePlayer != null;
+ }
+ destroyLocalPlayer();
+ // try opening uri locally before delegating to remote player
+ mLocalPlayer = new MediaPlayer();
+ try {
+ mLocalPlayer.setDataSource(mContext, mUri);
+ mLocalPlayer.setAudioAttributes(mAudioAttributes);
+ mLocalPlayer.setPreferredDevice(
+ mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
+ synchronized (mPlaybackSettingsLock) {
+ applyPlaybackProperties_sync();
+ }
+ if (mVolumeShaperConfig != null) {
+ mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
+ }
+ mLocalPlayer.prepare();
+
+ } catch (SecurityException | IOException e) {
+ destroyLocalPlayer();
+ if (!mAllowRemote) {
+ Log.w(TAG, "Remote playback not allowed: " + e);
+ }
+ }
+
+ if (LOGD) {
+ if (mLocalPlayer != null) {
+ Log.d(TAG, "Successfully created local player");
+ } else {
+ Log.d(TAG, "Problem opening; delegating to remote player");
+ }
+ }
+ Trace.endSection();
+ return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
}
/**
* Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
+ * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
+ * and if not URI has been set, it will assume no haptic channels are present.
* @hide
*/
public boolean hasHapticChannels() {
- return mApiImpl.hasHapticChannels();
+ // FIXME: support remote player, or internalize haptic channels support and remove entirely.
+ try {
+ android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+ if (mLocalPlayer != null) {
+ for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+ if (trackInfo.hasHapticChannels()) {
+ return true;
+ }
+ }
+ }
+ } finally {
+ android.os.Trace.endSection();
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a local player has been created for this ringtone.
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean hasLocalPlayer() {
+ return mLocalPlayer != null;
}
/**
@@ -255,7 +272,7 @@
* {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
*/
public AudioAttributes getAudioAttributes() {
- return mApiImpl.getAudioAttributes();
+ return mAudioAttributes;
}
/**
@@ -263,7 +280,10 @@
* @param looping whether to loop or not.
*/
public void setLooping(boolean looping) {
- mApiImpl.setLooping(looping);
+ synchronized (mPlaybackSettingsLock) {
+ mIsLooping = looping;
+ applyPlaybackProperties_sync();
+ }
}
/**
@@ -271,7 +291,9 @@
* @return true if this player loops when playing.
*/
public boolean isLooping() {
- return mApiImpl.isLooping();
+ synchronized (mPlaybackSettingsLock) {
+ return mIsLooping;
+ }
}
/**
@@ -280,7 +302,12 @@
* corresponds to no attenuation being applied.
*/
public void setVolume(float volume) {
- mApiImpl.setVolume(volume);
+ synchronized (mPlaybackSettingsLock) {
+ if (volume < 0.0f) { volume = 0.0f; }
+ if (volume > 1.0f) { volume = 1.0f; }
+ mVolume = volume;
+ applyPlaybackProperties_sync();
+ }
}
/**
@@ -288,7 +315,9 @@
* @return a value between 0.0f and 1.0f.
*/
public float getVolume() {
- return mApiImpl.getVolume();
+ synchronized (mPlaybackSettingsLock) {
+ return mVolume;
+ }
}
/**
@@ -299,7 +328,14 @@
* @see android.media.audiofx.HapticGenerator#isAvailable()
*/
public boolean setHapticGeneratorEnabled(boolean enabled) {
- return mApiImpl.setHapticGeneratorEnabled(enabled);
+ if (!HapticGenerator.isAvailable()) {
+ return false;
+ }
+ synchronized (mPlaybackSettingsLock) {
+ mHapticGeneratorEnabled = enabled;
+ applyPlaybackProperties_sync();
+ }
+ return true;
}
/**
@@ -307,7 +343,35 @@
* @return true if the HapticGenerator is enabled.
*/
public boolean isHapticGeneratorEnabled() {
- return mApiImpl.isHapticGeneratorEnabled();
+ synchronized (mPlaybackSettingsLock) {
+ return mHapticGeneratorEnabled;
+ }
+ }
+
+ /**
+ * Must be called synchronized on mPlaybackSettingsLock
+ */
+ private void applyPlaybackProperties_sync() {
+ if (mLocalPlayer != null) {
+ mLocalPlayer.setVolume(mVolume);
+ mLocalPlayer.setLooping(mIsLooping);
+ if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+ mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+ }
+ if (mHapticGenerator != null) {
+ mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+ }
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
+ try {
+ mRemotePlayer.setPlaybackProperties(
+ mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem setting playback properties: ", e);
+ }
+ } else {
+ Log.w(TAG,
+ "Neither local nor remote player available when applying playback properties");
+ }
}
/**
@@ -317,7 +381,8 @@
* @param context A context used for querying.
*/
public String getTitle(Context context) {
- return mApiImpl.getTitle(context);
+ if (mTitle != null) return mTitle;
+ return mTitle = getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
}
/**
@@ -391,24 +456,126 @@
return title;
}
+ /**
+ * Set {@link Uri} to be used for ringtone playback.
+ * {@link IRingtonePlayer}.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setUri(Uri uri) {
+ setUri(uri, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) {
+ mVolumeShaperConfig = volumeShaperConfig;
+ }
+
+ /**
+ * Set {@link Uri} to be used for ringtone playback. Attempts to open
+ * locally, otherwise will delegate playback to remote
+ * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required.
+ *
+ * @hide
+ */
+ public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+ mVolumeShaperConfig = volumeShaperConfig;
+ mUri = uri;
+ if (mUri == null) {
+ destroyLocalPlayer();
+ }
+ }
+
/** {@hide} */
@UnsupportedAppUsage
public Uri getUri() {
- return mApiImpl.getUri();
+ return mUri;
}
/**
* Plays the ringtone.
*/
public void play() {
- mApiImpl.play();
+ if (mLocalPlayer != null) {
+ // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
+ // (typically because ringer mode is vibrate).
+ if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
+ != 0) {
+ startLocalPlayer();
+ } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
+ // is haptic only ringtone
+ startLocalPlayer();
+ }
+ } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
+ final Uri canonicalUri = mUri.getCanonicalUri();
+ final boolean looping;
+ final float volume;
+ synchronized (mPlaybackSettingsLock) {
+ looping = mIsLooping;
+ volume = mVolume;
+ }
+ try {
+ mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes,
+ volume, looping, mVolumeShaperConfig);
+ } catch (RemoteException e) {
+ if (!playFallbackRingtone()) {
+ Log.w(TAG, "Problem playing ringtone: " + e);
+ }
+ }
+ } else {
+ if (!playFallbackRingtone()) {
+ Log.w(TAG, "Neither local nor remote playback available");
+ }
+ }
}
/**
* Stops a playing ringtone.
*/
public void stop() {
- mApiImpl.stop();
+ if (mLocalPlayer != null) {
+ destroyLocalPlayer();
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
+ try {
+ mRemotePlayer.stop(mRemoteToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem stopping ringtone: " + e);
+ }
+ }
+ }
+
+ private void destroyLocalPlayer() {
+ if (mLocalPlayer != null) {
+ if (mHapticGenerator != null) {
+ mHapticGenerator.release();
+ mHapticGenerator = null;
+ }
+ mLocalPlayer.setOnCompletionListener(null);
+ mLocalPlayer.reset();
+ mLocalPlayer.release();
+ mLocalPlayer = null;
+ mVolumeShaper = null;
+ synchronized (sActiveRingtones) {
+ sActiveRingtones.remove(this);
+ }
+ }
+ }
+
+ private void startLocalPlayer() {
+ if (mLocalPlayer == null) {
+ return;
+ }
+ synchronized (sActiveRingtones) {
+ sActiveRingtones.add(this);
+ }
+ mLocalPlayer.setOnCompletionListener(mCompletionListener);
+ mLocalPlayer.start();
+ if (mVolumeShaper != null) {
+ mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
+ }
}
/**
@@ -417,353 +584,87 @@
* @return True if playing, false otherwise.
*/
public boolean isPlaying() {
- return mApiImpl.isPlaying();
- }
-
- /**
- * Build a {@link Ringtone} to easily play sounds for ringtones, alarms and notifications.
- *
- * TODO: when un-hide, deprecate Ringtone: setAudioAttributes, setLooping,
- * setHapticGeneratorEnabled (no-effect if MEDIA_VIBRATION),
- * static RingtoneManager.getRingtone.
- * @hide
- */
- public static final class Builder {
- private final Context mContext;
- private final int mEnabledMedia;
- private Uri mUri;
- private final AudioAttributes mAudioAttributes;
- private boolean mUseExactAudioAttributes = false;
- // Not a static default since it doesn't really need to be in memory forever.
- private Injectables mInjectables = new Injectables();
- private VolumeShaper.Configuration mVolumeShaperConfig;
- private boolean mPreferBuiltinDevice = false;
- private boolean mAllowRemote = true;
- private boolean mHapticGeneratorEnabled = false;
- private float mInitialSoundVolume = 1.0f;
- private boolean mLooping = false;
- private VibrationEffect mVibrationEffect;
-
- /**
- * Constructs a builder to play the given media types from the mediaUri. If the mediaUri
- * is null (for example, an unset-setting), then fallback logic will dictate what plays.
- *
- * <p>When built, if the ringtone is already known to be a no-op, such as explicitly
- * silent, then the {@link #build} may return null.
- *
- * @param context The context for playing the ringtone.
- * @param enabledMedia Which media to play. Media not included is implicitly muted. Device
- * settings such as volume and vibrate-only may also affect which
- * media is played.
- * @param audioAttributes The attributes to use for playback, which affects the volumes and
- * settings that are applied.
- */
- public Builder(@NonNull Context context, @RingtoneMedia int enabledMedia,
- @NonNull AudioAttributes audioAttributes) {
- mContext = context;
- mEnabledMedia = enabledMedia;
- mAudioAttributes = audioAttributes;
- }
-
- /**
- * Inject test intercepters for static methods.
- * @hide
- */
- @NonNull
- public Builder setInjectables(Injectables injectables) {
- mInjectables = injectables;
- return this;
- }
-
- /**
- * The uri for the ringtone media to play. This is typically a user's preference for the
- * sound. If null, then it is treated as though the user's preference is unset and
- * fallback behavior, such as using the default ringtone setting, are used instead.
- *
- * When sound media is enabled, this is assumed to be a sound URI.
- */
- @NonNull
- public Builder setUri(@Nullable Uri uri) {
- mUri = uri;
- return this;
- }
-
- /**
- * Sets the VibrationEffect to use if vibration is enabled on this ringtone. The caller
- * should use {@link android.os.Vibrator#areVibrationFeaturesSupported} to ensure
- * that the effect is usable on this device, otherwise system defaults will be used.
- *
- * <p>Vibration will only happen if the Builder was created with media type
- * {@link Ringtone#MEDIA_VIBRATION} or {@link Ringtone#MEDIA_SOUND_AND_VIBRATION}, and
- * the application has the {@link android.Manifest.permission#VIBRATE} permission.
- *
- * <p>If the Ringtone is looping when it is played, then the VibrationEffect will be
- * modified to loop. Similarly, if the ringtone is not looping, a repeating
- * VibrationEffect will be modified to be non-repeating when the ringtone is played. Calls
- * to {@link Ringtone#setLooping} after the ringtone has started playing will stop a looping
- * vibration, but has no effect otherwise: specifically it will not restart vibration.
- */
- @NonNull
- public Builder setVibrationEffect(@NonNull VibrationEffect effect) {
- mVibrationEffect = effect;
- return this;
- }
-
- /**
- * Sets whether the resulting ringtone should loop until {@link Ringtone#stop()} is called,
- * or just play once.
- */
- @NonNull
- public Builder setLooping(boolean looping) {
- mLooping = looping;
- return this;
- }
-
- /**
- * Sets the VolumeShaper.Configuration to apply to the ringtone.
- * @hide
- */
- @NonNull
- public Builder setVolumeShaperConfig(
- @Nullable VolumeShaper.Configuration volumeShaperConfig) {
- mVolumeShaperConfig = volumeShaperConfig;
- return this;
- }
-
- /**
- * Whether to enable or disable the haptic generator.
- * @hide
- */
- @NonNull
- public Builder setEnableHapticGenerator(boolean enabled) {
- // Note that this property is mutable (but deprecated) on the Ringtone class itself.
- mHapticGeneratorEnabled = enabled;
- return this;
- }
-
- /**
- * Sets the initial sound volume for the ringtone.
- */
- @NonNull
- public Builder setInitialSoundVolume(float initialSoundVolume) {
- mInitialSoundVolume = initialSoundVolume;
- return this;
- }
-
- /**
- * Sets the preferred device of the ringtone playback to the built-in device. This is
- * only for use by the system server with known-good Uris.
- * @hide
- */
- @NonNull
- public Builder setPreferBuiltinDevice() {
- mPreferBuiltinDevice = true;
- mAllowRemote = false; // Already in system.
- return this;
- }
-
- /**
- * Indicates that {@link AudioAttributes#areHapticChannelsMuted()} on the builder's
- * AudioAttributes should not be overridden. This is used to enable legacy behavior of
- * calling {@link Ringtone#setAudioAttributes} on an already-created ringtone, and can in
- * turn cause vibration during a "sound-only" session or can suppress audio-coupled
- * haptics that would usually take priority (therefore potentially falling back to
- * the VibrationEffect or system defaults).
- *
- * <p>Without this setting, the haptic channels will be automatically muted or not by the
- * Ringtone according to whether vibration is enabled or not.
- *
- * <p>This is for internal-use only. New applications should configure the vibration
- * behavior explicitly with the (TODO: future RingtoneSetting.setVibrationSource).
- * Handling haptic channels outside Ringtone leads to extra loads of the sound uri.
- * @hide
- */
- @NonNull
- public Builder setUseExactAudioAttributes(boolean useExactAttrs) {
- mUseExactAudioAttributes = useExactAttrs;
- return this;
- }
-
- /**
- * Prevent fallback to the remote service. This is primarily intended for use within the
- * remote IRingtonePlayer service itself, to avoid loops.
- * @hide
- */
- @NonNull
- public Builder setLocalOnly() {
- mAllowRemote = false;
- return this;
- }
-
- private boolean isVibrationEnabledAndAvailable() {
- if ((mEnabledMedia & MEDIA_VIBRATION) == 0) {
- return false;
- }
- Vibrator vibrator = mContext.getSystemService(Vibrator.class);
- if (!vibrator.hasVibrator()) {
- return false;
- }
- if (mContext.checkSelfPermission(Manifest.permission.VIBRATE)
- != PackageManager.PERMISSION_GRANTED) {
- Log.w(TAG, "Ringtone requests vibration enabled, but no VIBRATE permission");
- return false;
- }
- return true;
- }
-
- /**
- * Returns the built Ringtone, or null if there was a problem loading the Uri and there
- * are no fallback options available.
- */
- @Nullable
- public Ringtone build() {
- @Ringtone.RingtoneMedia int effectiveEnabledMedia = mEnabledMedia;
- VibrationEffect effectiveVibrationEffect = mVibrationEffect;
-
- // Normalize media to that supported on this SDK level.
- if (effectiveEnabledMedia != (effectiveEnabledMedia & MEDIA_ALL)) {
- Log.e(TAG, "Unsupported media type: " + effectiveEnabledMedia);
- effectiveEnabledMedia = effectiveEnabledMedia & MEDIA_ALL;
- }
- final boolean effectiveHapticGenerator;
- final boolean hapticChannelsSupported;
- AudioAttributes effectiveAudioAttributes = mAudioAttributes;
- final boolean hapticChannelsMuted = mAudioAttributes.areHapticChannelsMuted();
- if (!isVibrationEnabledAndAvailable()) {
- // Vibration isn't active: turn off everything that might cause extra work.
- effectiveEnabledMedia &= ~MEDIA_VIBRATION;
- effectiveHapticGenerator = false;
- effectiveVibrationEffect = null;
- if (!mUseExactAudioAttributes && !hapticChannelsMuted) {
- effectiveAudioAttributes = new AudioAttributes.Builder(effectiveAudioAttributes)
- .setHapticChannelsMuted(true)
- .build();
- }
- } else {
- // Vibration is active.
- effectiveHapticGenerator =
- mHapticGeneratorEnabled && mInjectables.isHapticGeneratorAvailable();
- hapticChannelsSupported = mInjectables.isHapticPlaybackSupported();
- // Haptic channels are preferred if they are available, and not explicitly muted.
- // We won't know if haptic channels are available until loading the media player,
- // and since the media player needs to be reset to change audio attributes, then
- // we proactively enable the channels - it won't matter if they aren't present.
- if (!mUseExactAudioAttributes) {
- boolean shouldBeMuted = effectiveHapticGenerator || !hapticChannelsSupported;
- if (shouldBeMuted != hapticChannelsMuted) {
- effectiveAudioAttributes =
- new AudioAttributes.Builder(effectiveAudioAttributes)
- .setHapticChannelsMuted(shouldBeMuted)
- .build();
- }
- }
- // If no contextual vibration, then try loading the default one for the URI.
- if (mVibrationEffect == null && mUri != null) {
- effectiveVibrationEffect = VibrationEffect.get(mUri, mContext);
- }
- }
+ if (mLocalPlayer != null) {
+ return mLocalPlayer.isPlaying();
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
try {
- Ringtone ringtone = new Ringtone(this, effectiveEnabledMedia,
- effectiveAudioAttributes, effectiveVibrationEffect,
- effectiveHapticGenerator);
- if (ringtone.reinitializeActivePlayer()) {
- return ringtone;
- } else {
- Log.e(TAG, "Failed to open ringtone " + mUri);
- return null;
- }
- } catch (Exception ex) {
- // Catching Exception isn't great, but was done in the old
- // RingtoneManager.getRingtone and hides errors like DocumentsProvider throwing
- // IllegalArgumentException instead of FileNotFoundException, and also robolectric
- // failures when ShadowMediaPlayer wasn't pre-informed of the ringtone.
- Log.e(TAG, "Failed while opening ringtone " + mUri, ex);
- return null;
+ return mRemotePlayer.isPlaying(mRemoteToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem checking ringtone: " + e);
+ return false;
}
- }
- }
-
- /**
- * Interface for intercepting static methods and constructors, for unit testing only.
- * @hide
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static class Injectables {
- /** Intercept {@code new MediaPlayer()}. */
- @NonNull
- public MediaPlayer newMediaPlayer() {
- return new MediaPlayer();
- }
-
- /** Intercept {@link HapticGenerator#isAvailable}. */
- public boolean isHapticGeneratorAvailable() {
- return HapticGenerator.isAvailable();
- }
-
- /**
- * Intercept {@link HapticGenerator#create} using
- * {@link MediaPlayer#getAudioSessionId()} from the given media player.
- */
- @Nullable
- public HapticGenerator createHapticGenerator(@NonNull MediaPlayer mediaPlayer) {
- return HapticGenerator.create(mediaPlayer.getAudioSessionId());
- }
-
- /** Returns the result of {@link AudioManager#isHapticPlaybackSupported()}. */
- public boolean isHapticPlaybackSupported() {
- return AudioManager.isHapticPlaybackSupported();
- }
-
- /**
- * Returns whether the MediaPlayer tracks have haptic channels. This is the same as
- * AudioManager.hasHapticChannels, except it uses an already prepared MediaPlayer to avoid
- * loading the metadata a second time.
- */
- public boolean hasHapticChannels(MediaPlayer mp) {
- try {
- Trace.beginSection("Ringtone.hasHapticChannels");
- for (MediaPlayer.TrackInfo trackInfo : mp.getTrackInfo()) {
- if (trackInfo.hasHapticChannels()) {
- return true;
- }
- }
- } finally {
- Trace.endSection();
- }
+ } else {
+ Log.w(TAG, "Neither local nor remote playback available");
return false;
}
-
}
- /**
- * Interface for alternative Ringtone implementations. See the public Ringtone methods that
- * delegate to these for documentation.
- * @hide
- */
- interface ApiInterface {
- void setStreamType(int streamType);
- int getStreamType();
- void setAudioAttributes(AudioAttributes attributes);
- boolean getPreferBuiltinDevice();
- VolumeShaper.Configuration getVolumeShaperConfig();
- boolean isLocalOnly();
- boolean isUsingRemotePlayer();
- boolean reinitializeActivePlayer();
- boolean hasHapticChannels();
- AudioAttributes getAudioAttributes();
- void setLooping(boolean looping);
- boolean isLooping();
- void setVolume(float volume);
- float getVolume();
- boolean setHapticGeneratorEnabled(boolean enabled);
- boolean isHapticGeneratorEnabled();
- String getTitle(Context context);
- Uri getUri();
- void play();
- void stop();
- boolean isPlaying();
- // V2 future-public methods
- @RingtoneMedia int getEnabledMedia();
- VibrationEffect getVibrationEffect();
+ private boolean playFallbackRingtone() {
+ int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
+ if (mAudioManager.getStreamVolume(streamType) == 0) {
+ return false;
+ }
+ int ringtoneType = RingtoneManager.getDefaultType(mUri);
+ if (ringtoneType != -1 &&
+ RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
+ Log.w(TAG, "not playing fallback for " + mUri);
+ return false;
+ }
+ // Default ringtone, try fallback ringtone.
+ try {
+ AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
+ com.android.internal.R.raw.fallbackring);
+ if (afd == null) {
+ Log.e(TAG, "Could not load fallback ringtone");
+ return false;
+ }
+ mLocalPlayer = new MediaPlayer();
+ if (afd.getDeclaredLength() < 0) {
+ mLocalPlayer.setDataSource(afd.getFileDescriptor());
+ } else {
+ mLocalPlayer.setDataSource(afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
+ }
+ mLocalPlayer.setAudioAttributes(mAudioAttributes);
+ synchronized (mPlaybackSettingsLock) {
+ applyPlaybackProperties_sync();
+ }
+ if (mVolumeShaperConfig != null) {
+ mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
+ }
+ mLocalPlayer.prepare();
+ startLocalPlayer();
+ afd.close();
+ } catch (IOException ioe) {
+ destroyLocalPlayer();
+ Log.e(TAG, "Failed to open fallback ringtone");
+ return false;
+ } catch (NotFoundException nfe) {
+ Log.e(TAG, "Fallback ringtone does not exist");
+ return false;
+ }
+ return true;
+ }
+
+ void setTitle(String title) {
+ mTitle = title;
+ }
+
+ @Override
+ protected void finalize() {
+ if (mLocalPlayer != null) {
+ mLocalPlayer.release();
+ }
+ }
+
+ class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ synchronized (sActiveRingtones) {
+ sActiveRingtones.remove(Ringtone.this);
+ }
+ mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
+ }
}
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index b5a9ae2..3432b3f 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.IntDef;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -30,27 +30,24 @@
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.StaleDataException;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.vibrator.Flags;
-import android.os.vibrator.persistence.VibrationXmlParser;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AudioColumns;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.provider.Settings.System;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.database.SortCursor;
@@ -61,8 +58,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -122,53 +117,6 @@
public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER";
/**
- * Given to the ringtone picker as a string that represents the category of ringtone picker that
- * should be used. This value should also be returned once a ringtone is selected.
- * <p>
- * The categories are:
- * <li>{@link #CATEGORY_RINGTONE_PICKER_SOUND}
- * <li>{@link #CATEGORY_RINGTONE_PICKER_VIBRATION}
- * <li>{@link #CATEGORY_RINGTONE_PICKER_RINGTONE}
- * <li>{@link Intent#CATEGORY_DEFAULT}
- *
- * <p> If the category is {@link Intent#CATEGORY_DEFAULT} or absent, then the picker will
- * default to a sound-only ringtone picker.
- *
- * <p> If the selected category was not supported, then the returned category will be null.
- *
- * @hide
- */
- public static final String EXTRA_RINGTONE_PICKER_CATEGORY =
- "android.intent.extra.ringtone.RINGTONE_PICKER_CATEGORY";
-
- /**
- * A sound-only ringtone picker.
- *
- * @hide
- * @see #EXTRA_RINGTONE_PICKER_CATEGORY
- */
- public static final String CATEGORY_RINGTONE_PICKER_SOUND =
- "android.net.category.RINGTONE_PICKER_SOUND";
-
- /**
- * A vibration-only ringtone picker.
- *
- * @hide
- * @see #EXTRA_RINGTONE_PICKER_CATEGORY
- */
- public static final String CATEGORY_RINGTONE_PICKER_VIBRATION =
- "android.net.category.RINGTONE_PICKER_VIBRATION";
-
- /**
- * A combined sound and vibration ringtone picker.
- *
- * @hide
- * @see #EXTRA_RINGTONE_PICKER_CATEGORY
- */
- public static final String CATEGORY_RINGTONE_PICKER_RINGTONE =
- "android.net.category.RINGTONE_PICKER_RINGTONE";
-
- /**
* Given to the ringtone picker as a boolean. Whether to show an item for
* "Default".
*
@@ -209,18 +157,6 @@
*/
public static final String EXTRA_RINGTONE_EXISTING_URI =
"android.intent.extra.ringtone.EXISTING_URI";
-
- /**
- * Similar to #EXTRA_RINGTONE_EXISTING_URI but the {@link Uri} can include both sound and
- * vibration.
- * <p>This can include silent sound/vibration explicitly by setting that part of the URI to
- * null.
- *
- * @hide
- * @see #ACTION_RINGTONE_PICKER
- */
- public static final String EXTRA_RINGTONE_EXISTING_RINGTONE_URI =
- "android.intent.extra.ringtone.RINGTONE_EXISTING_RINGTONE_URI";
/**
* Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
@@ -273,30 +209,21 @@
*/
public static final String EXTRA_RINGTONE_PICKED_URI =
"android.intent.extra.ringtone.PICKED_URI";
-
- /**
- * Declares the allowed types of media for this RingtoneManager.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "MEDIA_", value = {
- Ringtone.MEDIA_SOUND,
- Ringtone.MEDIA_VIBRATION,
- })
- public @interface MediaType {}
-
+
// Make sure the column ordering and then ..._COLUMN_INDEX are in sync
- private static final String[] MEDIA_AUDIO_COLUMNS = new String[] {
+ private static final String[] INTERNAL_COLUMNS = new String[] {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE_KEY,
};
- private static final String[] MEDIA_VIBRATION_COLUMNS = new String[]{
- MediaStore.Files.FileColumns._ID,
- MediaStore.Files.FileColumns.TITLE,
+ private static final String[] MEDIA_COLUMNS = new String[] {
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.TITLE_KEY,
};
/**
@@ -324,9 +251,7 @@
private Cursor mCursor;
private int mType = TYPE_RINGTONE;
- @MediaType
- private int mMediaType = Ringtone.MEDIA_SOUND;
-
+
/**
* If a column (item from this list) exists in the Cursor, its value must
* be true (value of 1) for the row to be returned.
@@ -393,41 +318,6 @@
}
/**
- * Sets the media type that will be listed by the RingtoneManager.
- *
- * <p>This method should be called before calling {@link RingtoneManager#getCursor()}.
- *
- * @hide
- */
- public void setMediaType(@MediaType int mediaType) {
- if (mCursor != null) {
- throw new IllegalStateException(
- "Setting media should be done before calling getCursor().");
- }
-
- switch (mediaType) {
- case Ringtone.MEDIA_SOUND:
- case Ringtone.MEDIA_VIBRATION:
- mMediaType = mediaType;
- break;
- default:
- throw new IllegalArgumentException("Unsupported media type " + mediaType);
- }
- }
-
- /**
- * Returns the RingtoneManagers media type.
- *
- * @return the media type.
- * @see #setMediaType
- * @hide
- */
- @MediaType
- public int getMediaType() {
- return mMediaType;
- }
-
- /**
* Sets which type(s) of ringtones will be listed by this.
*
* @param type The type(s), one or more of {@link #TYPE_RINGTONE},
@@ -465,25 +355,6 @@
}
}
- /** @hide */
- @NonNull
- public static AudioAttributes getDefaultAudioAttributes(int ringtoneType) {
- AudioAttributes.Builder builder = new AudioAttributes.Builder();
- switch (ringtoneType) {
- case TYPE_ALARM:
- builder.setUsage(AudioAttributes.USAGE_ALARM);
- break;
- case TYPE_NOTIFICATION:
- builder.setUsage(AudioAttributes.USAGE_NOTIFICATION);
- break;
- default: // ringtone or all
- builder.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
- break;
- }
- builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
- return builder.build();
- }
-
/**
* Whether retrieving another {@link Ringtone} will stop playing the
* previously retrieved {@link Ringtone}.
@@ -564,19 +435,19 @@
return mCursor;
}
- ArrayList<Cursor> cursors = new ArrayList<>();
-
- cursors.add(queryMediaStore(/* internal= */ true));
- cursors.add(queryMediaStore(/* internal= */ false));
+ ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>();
+ ringtoneCursors.add(getInternalRingtones());
+ ringtoneCursors.add(getMediaRingtones());
if (mIncludeParentRingtones) {
Cursor parentRingtonesCursor = getParentProfileRingtones();
if (parentRingtonesCursor != null) {
- cursors.add(parentRingtonesCursor);
+ ringtoneCursors.add(parentRingtonesCursor);
}
}
- return mCursor = new SortCursor(cursors.toArray(new Cursor[cursors.size()]),
- getSortOrderForMedia(mMediaType));
+
+ return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]),
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
}
private Cursor getParentProfileRingtones() {
@@ -588,7 +459,9 @@
// We don't need to re-add the internal ringtones for the work profile since
// they are the same as the personal profile. We just need the external
// ringtones.
- return queryMediaStore(parentContext, /* internal= */ false);
+ final Cursor res = getMediaRingtones(parentContext);
+ return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id));
}
}
return null;
@@ -606,32 +479,11 @@
mPreviousRingtone.stop();
}
- Ringtone ringtone;
- Uri positionUri = getRingtoneUri(position);
- if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
- mPreviousRingtone = new Ringtone.Builder(
- mContext, mMediaType, getDefaultAudioAttributes(mType))
- .setUri(positionUri)
- .build();
- } else {
- mPreviousRingtone = createRingtoneV1WithStreamType(mContext, positionUri,
- inferStreamType(), /* volumeShaperConfig= */ null);
- }
+ mPreviousRingtone =
+ getRingtone(mContext, getRingtoneUri(position), inferStreamType(), true);
return mPreviousRingtone;
}
- private static Ringtone createRingtoneV1WithStreamType(
- final Context context, Uri ringtoneUri, int streamType,
- @Nullable VolumeShaper.Configuration volumeShaperConfig) {
- try {
- return Ringtone.createV1WithCustomStreamType(context, streamType, ringtoneUri,
- volumeShaperConfig);
- } catch (Exception ex) {
- Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
- }
- return null;
- }
-
/**
* Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}.
*
@@ -783,13 +635,11 @@
*/
public static Uri getValidRingtoneUri(Context context) {
final RingtoneManager rm = new RingtoneManager(context);
-
- Uri uri = getValidRingtoneUriFromCursorAndClose(context,
- rm.queryMediaStore(/* internal= */ true));
+
+ Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
if (uri == null) {
- uri = getValidRingtoneUriFromCursorAndClose(context,
- rm.queryMediaStore(/* internal= */ false));
+ uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
}
return uri;
@@ -810,26 +660,28 @@
}
}
- private Cursor queryMediaStore(boolean internal) {
- return queryMediaStore(mContext, internal);
+ @UnsupportedAppUsage
+ private Cursor getInternalRingtones() {
+ final Cursor res = query(
+ MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
+ constructBooleanTrueWhereClause(mFilterColumns),
+ null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+ return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
}
- private Cursor queryMediaStore(Context context, boolean internal) {
- Uri contentUri = getContentUriForMedia(mMediaType, internal);
- String[] columns =
- mMediaType == Ringtone.MEDIA_VIBRATION ? MEDIA_VIBRATION_COLUMNS
- : MEDIA_AUDIO_COLUMNS;
- String whereClause = getWhereClauseForMedia(mMediaType, mFilterColumns);
- String sortOrder = getSortOrderForMedia(mMediaType);
+ private Cursor getMediaRingtones() {
+ final Cursor res = getMediaRingtones(mContext);
+ return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
+ }
- Cursor cursor = query(contentUri, columns, whereClause, /* selectionArgs= */ null,
- sortOrder, context);
-
- if (context.getUserId() != mContext.getUserId()) {
- contentUri = ContentProvider.maybeAddUserId(contentUri, context.getUserId());
- }
-
- return new ExternalRingtonesCursorWrapper(cursor, contentUri);
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private Cursor getMediaRingtones(Context context) {
+ // MediaStore now returns ringtones on other storage devices, even when
+ // we don't have storage or audio permissions
+ return query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
+ constructBooleanTrueWhereClause(mFilterColumns), null,
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context);
}
private void setFilterColumnsList(int type) {
@@ -848,56 +700,6 @@
columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
}
}
-
- /**
- * Returns the sort order for the specified media.
- *
- * @param media The RingtoneManager media type.
- * @return The sort order column.
- */
- private static String getSortOrderForMedia(@MediaType int media) {
- return media == Ringtone.MEDIA_VIBRATION ? MediaStore.Files.FileColumns.TITLE
- : MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
- }
-
- /**
- * Returns the content URI based on the specified media and whether it's internal or external
- * storage.
- *
- * @param media The RingtoneManager media type.
- * @param internal Whether it's for internal or external storage.
- * @return The media content URI.
- */
- private static Uri getContentUriForMedia(@MediaType int media, boolean internal) {
- switch (media) {
- case Ringtone.MEDIA_VIBRATION:
- return MediaStore.Files.getContentUri(
- internal ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL);
- case Ringtone.MEDIA_SOUND:
- return internal ? MediaStore.Audio.Media.INTERNAL_CONTENT_URI
- : MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- default:
- throw new IllegalArgumentException("Unsupported media type " + media);
- }
- }
-
- /**
- * Constructs a where clause based on the media type. This will be used to find all matching
- * sound or vibration files.
- *
- * @param media The RingtoneManager media type.
- * @param columns The columns that must be true, when media type is {@link Ringtone#MEDIA_SOUND}
- * @return The where clause.
- */
- private static String getWhereClauseForMedia(@MediaType int media, List<String> columns) {
- // TODO(b/296213309): Filtering by ringtone-type isn't supported yet for vibrations.
- if (media == Ringtone.MEDIA_VIBRATION) {
- return TextUtils.formatSimple("(%s='%s')", MediaStore.Files.FileColumns.MIME_TYPE,
- VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE);
- }
-
- return constructBooleanTrueWhereClause(columns);
- }
/**
* Constructs a where clause that consists of at least one column being 1
@@ -927,6 +729,14 @@
return sb.toString();
}
+
+ private Cursor query(Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ return query(uri, projection, selection, selectionArgs, sortOrder, mContext);
+ }
private Cursor query(Uri uri,
String[] projection,
@@ -954,14 +764,40 @@
* @return A {@link Ringtone} for the given URI, or null.
*/
public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
- if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
- return new Ringtone.Builder(
- context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1))
- .setUri(ringtoneUri)
- .build();
- } else {
- return createRingtoneV1WithStreamType(context, ringtoneUri, -1, null);
- }
+ // Don't set the stream type
+ return getRingtone(context, ringtoneUri, -1, true);
+ }
+
+ /**
+ * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
+ * <p>
+ * If the given URI cannot be opened for any reason, this method will
+ * attempt to fallback on another sound. If it cannot find any, it will
+ * return null.
+ *
+ * @param context A context used to query.
+ * @param ringtoneUri The {@link Uri} of a sound or ringtone.
+ * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
+ * @return A {@link Ringtone} for the given URI, or null.
+ *
+ * @hide
+ */
+ public static Ringtone getRingtone(
+ final Context context, Uri ringtoneUri,
+ @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+ // Don't set the stream type
+ return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, true);
+ }
+
+ /**
+ * @hide
+ */
+ public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
+ @Nullable VolumeShaper.Configuration volumeShaperConfig,
+ boolean createLocalMediaPlayer) {
+ // Don't set the stream type
+ return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig,
+ createLocalMediaPlayer);
}
/**
@@ -970,23 +806,64 @@
public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
@Nullable VolumeShaper.Configuration volumeShaperConfig,
AudioAttributes audioAttributes) {
- // TODO: move caller(s) away from this method: inline the builder call.
- if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
- return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes)
- .setUri(ringtoneUri)
- .setVolumeShaperConfig(volumeShaperConfig)
- .setUseExactAudioAttributes(true) // May be using audio-coupled via attrs
- .build();
- } else {
- try {
- return Ringtone.createV1WithCustomAudioAttributes(context, audioAttributes,
- ringtoneUri, volumeShaperConfig, /* allowRemote= */ true);
- } catch (Exception ex) {
- // Match broad catching of createRingtoneV1.
- Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
+ // Don't set the stream type
+ Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
+ volumeShaperConfig, false);
+ if (ringtone != null) {
+ ringtone.setAudioAttributesField(audioAttributes);
+ if (!ringtone.createLocalMediaPlayer()) {
+ Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
return null;
}
}
+ return ringtone;
+ }
+
+ //FIXME bypass the notion of stream types within the class
+ /**
+ * Returns a {@link Ringtone} for a given sound URI on the given stream
+ * type. Normally, if you change the stream type on the returned
+ * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
+ * an optimized route to avoid that.
+ *
+ * @param streamType The stream type for the ringtone, or -1 if it should
+ * not be set (and the default used instead).
+ * @param createLocalMediaPlayer when true, the ringtone returned will be fully
+ * created otherwise, it will require the caller to create the media player manually
+ * {@link Ringtone#createLocalMediaPlayer()} in order to play the Ringtone.
+ * @see #getRingtone(Context, Uri)
+ */
+ @UnsupportedAppUsage
+ private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
+ boolean createLocalMediaPlayer) {
+ return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */,
+ createLocalMediaPlayer);
+ }
+
+ private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
+ @Nullable VolumeShaper.Configuration volumeShaperConfig,
+ boolean createLocalMediaPlayer) {
+ try {
+ final Ringtone r = new Ringtone(context, true);
+ if (streamType >= 0) {
+ //FIXME deprecated call
+ r.setStreamType(streamType);
+ }
+
+ r.setVolumeShaperConfig(volumeShaperConfig);
+ r.setUri(ringtoneUri, volumeShaperConfig);
+ if (createLocalMediaPlayer) {
+ if (!r.createLocalMediaPlayer()) {
+ Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+ return null;
+ }
+ }
+ return r;
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
+ }
+
+ return null;
}
/**
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
deleted file mode 100644
index 3c54d4a..0000000
--- a/media/java/android/media/RingtoneV1.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources.NotFoundException;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.os.VibrationEffect;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Hosts original Ringtone implementation, retained for flagging large builder+vibration features
- * in RingtoneV2.java. This does not support new features in the V2 builder.
- *
- * Only modified methods are moved here.
- *
- * @hide
- */
-class RingtoneV1 implements Ringtone.ApiInterface {
- private static final String TAG = "RingtoneV1";
- private static final boolean LOGD = true;
-
- private static final String[] MEDIA_COLUMNS = new String[] {
- MediaStore.Audio.Media._ID,
- MediaStore.Audio.Media.TITLE
- };
- /** Selection that limits query results to just audio files */
- private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
- + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
-
- // keep references on active Ringtones until stopped or completion listener called.
- private static final ArrayList<RingtoneV1> sActiveRingtones = new ArrayList<>();
-
- private final Context mContext;
- private final AudioManager mAudioManager;
- private VolumeShaper.Configuration mVolumeShaperConfig;
- private VolumeShaper mVolumeShaper;
-
- /**
- * Flag indicating if we're allowed to fall back to remote playback using
- * {@link #mRemotePlayer}. Typically this is false when we're the remote
- * player and there is nobody else to delegate to.
- */
- private final boolean mAllowRemote;
- private final IRingtonePlayer mRemotePlayer;
- private final Binder mRemoteToken;
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private MediaPlayer mLocalPlayer;
- private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
- private HapticGenerator mHapticGenerator;
-
- @UnsupportedAppUsage
- private Uri mUri;
- private String mTitle;
-
- private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build();
- private boolean mPreferBuiltinDevice;
- // playback properties, use synchronized with mPlaybackSettingsLock
- private boolean mIsLooping = false;
- private float mVolume = 1.0f;
- private boolean mHapticGeneratorEnabled = false;
- private final Object mPlaybackSettingsLock = new Object();
-
- /** {@hide} */
- @UnsupportedAppUsage
- public RingtoneV1(Context context, boolean allowRemote) {
- mContext = context;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mAllowRemote = allowRemote;
- mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
- mRemoteToken = allowRemote ? new Binder() : null;
- }
-
- /**
- * Sets the stream type where this ringtone will be played.
- *
- * @param streamType The stream, see {@link AudioManager}.
- * @deprecated use {@link #setAudioAttributes(AudioAttributes)}
- */
- @Deprecated
- public void setStreamType(int streamType) {
- PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()");
- setAudioAttributes(new AudioAttributes.Builder()
- .setInternalLegacyStreamType(streamType)
- .build());
- }
-
- /**
- * Gets the stream type where this ringtone will be played.
- *
- * @return The stream type, see {@link AudioManager}.
- * @deprecated use of stream types is deprecated, see
- * {@link #setAudioAttributes(AudioAttributes)}
- */
- @Deprecated
- public int getStreamType() {
- return AudioAttributes.toLegacyStreamType(mAudioAttributes);
- }
-
- /**
- * Sets the {@link AudioAttributes} for this ringtone.
- * @param attributes the non-null attributes characterizing this ringtone.
- */
- public void setAudioAttributes(AudioAttributes attributes)
- throws IllegalArgumentException {
- setAudioAttributesField(attributes);
- // The audio attributes have to be set before the media player is prepared.
- // Re-initialize it.
- setUri(mUri, mVolumeShaperConfig);
- reinitializeActivePlayer();
- }
-
- /**
- * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create
- * the media player.
- * @hide
- */
- public void setAudioAttributesField(@Nullable AudioAttributes attributes) {
- if (attributes == null) {
- throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
- }
- mAudioAttributes = attributes;
- }
-
- /**
- * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
- * the one on which outgoing audio for SIM calls is played.
- *
- * @param audioManager the audio manage.
- * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
- * none can be found.
- */
- private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
- AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
- for (AudioDeviceInfo device : deviceList) {
- if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- return device;
- }
- }
- return null;
- }
-
- /**
- * Sets the preferred device of the ringtong playback to the built-in device.
- *
- * @hide
- */
- public boolean preferBuiltinDevice(boolean enable) {
- mPreferBuiltinDevice = enable;
- if (mLocalPlayer == null) {
- return true;
- }
- return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
- }
-
- /**
- * Creates a local media player for the ringtone using currently set attributes.
- * @return true if media player creation succeeded or is deferred,
- * false if it did not succeed and can't be tried remotely.
- * @hide
- */
- public boolean reinitializeActivePlayer() {
- Trace.beginSection("reinitializeActivePlayer");
- if (mUri == null) {
- Log.e(TAG, "Could not create media player as no URI was provided.");
- return mAllowRemote && mRemotePlayer != null;
- }
- destroyLocalPlayer();
- // try opening uri locally before delegating to remote player
- mLocalPlayer = new MediaPlayer();
- try {
- mLocalPlayer.setDataSource(mContext, mUri);
- mLocalPlayer.setAudioAttributes(mAudioAttributes);
- mLocalPlayer.setPreferredDevice(
- mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
- synchronized (mPlaybackSettingsLock) {
- applyPlaybackProperties_sync();
- }
- if (mVolumeShaperConfig != null) {
- mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
- }
- mLocalPlayer.prepare();
-
- } catch (SecurityException | IOException e) {
- destroyLocalPlayer();
- if (!mAllowRemote) {
- Log.w(TAG, "Remote playback not allowed: " + e);
- }
- }
-
- if (LOGD) {
- if (mLocalPlayer != null) {
- Log.d(TAG, "Successfully created local player");
- } else {
- Log.d(TAG, "Problem opening; delegating to remote player");
- }
- }
- Trace.endSection();
- return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
- }
-
- /**
- * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
- * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
- * and if not URI has been set, it will assume no haptic channels are present.
- * @hide
- */
- public boolean hasHapticChannels() {
- // FIXME: support remote player, or internalize haptic channels support and remove entirely.
- try {
- android.os.Trace.beginSection("Ringtone.hasHapticChannels");
- if (mLocalPlayer != null) {
- for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
- if (trackInfo.hasHapticChannels()) {
- return true;
- }
- }
- }
- } finally {
- android.os.Trace.endSection();
- }
- return false;
- }
-
- /**
- * Returns whether a local player has been created for this ringtone.
- * @hide
- */
- @VisibleForTesting
- public boolean hasLocalPlayer() {
- return mLocalPlayer != null;
- }
-
- public @Ringtone.RingtoneMedia int getEnabledMedia() {
- return Ringtone.MEDIA_SOUND; // RingtoneV2 only
- }
-
- public VibrationEffect getVibrationEffect() {
- return null; // RingtoneV2 only
- }
-
- /**
- * Returns the {@link AudioAttributes} used by this object.
- * @return the {@link AudioAttributes} that were set with
- * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
- */
- public AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
- }
-
- /**
- * Sets the player to be looping or non-looping.
- * @param looping whether to loop or not.
- */
- public void setLooping(boolean looping) {
- synchronized (mPlaybackSettingsLock) {
- mIsLooping = looping;
- applyPlaybackProperties_sync();
- }
- }
-
- /**
- * Returns whether the looping mode was enabled on this player.
- * @return true if this player loops when playing.
- */
- public boolean isLooping() {
- synchronized (mPlaybackSettingsLock) {
- return mIsLooping;
- }
- }
-
- /**
- * Sets the volume on this player.
- * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
- * corresponds to no attenuation being applied.
- */
- public void setVolume(float volume) {
- synchronized (mPlaybackSettingsLock) {
- if (volume < 0.0f) { volume = 0.0f; }
- if (volume > 1.0f) { volume = 1.0f; }
- mVolume = volume;
- applyPlaybackProperties_sync();
- }
- }
-
- /**
- * Returns the volume scalar set on this player.
- * @return a value between 0.0f and 1.0f.
- */
- public float getVolume() {
- synchronized (mPlaybackSettingsLock) {
- return mVolume;
- }
- }
-
- /**
- * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
- * only be enabled on devices that support the effect.
- *
- * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
- * @see android.media.audiofx.HapticGenerator#isAvailable()
- */
- public boolean setHapticGeneratorEnabled(boolean enabled) {
- if (!HapticGenerator.isAvailable()) {
- return false;
- }
- synchronized (mPlaybackSettingsLock) {
- mHapticGeneratorEnabled = enabled;
- applyPlaybackProperties_sync();
- }
- return true;
- }
-
- /**
- * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
- * @return true if the HapticGenerator is enabled.
- */
- public boolean isHapticGeneratorEnabled() {
- synchronized (mPlaybackSettingsLock) {
- return mHapticGeneratorEnabled;
- }
- }
-
- /**
- * Must be called synchronized on mPlaybackSettingsLock
- */
- private void applyPlaybackProperties_sync() {
- if (mLocalPlayer != null) {
- mLocalPlayer.setVolume(mVolume);
- mLocalPlayer.setLooping(mIsLooping);
- if (mHapticGenerator == null && mHapticGeneratorEnabled) {
- mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
- }
- if (mHapticGenerator != null) {
- mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
- }
- } else if (mAllowRemote && (mRemotePlayer != null)) {
- try {
- mRemotePlayer.setPlaybackProperties(
- mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem setting playback properties: ", e);
- }
- } else {
- Log.w(TAG,
- "Neither local nor remote player available when applying playback properties");
- }
- }
-
- /**
- * Returns a human-presentable title for ringtone. Looks in media
- * content provider. If not in either, uses the filename
- *
- * @param context A context used for querying.
- */
- public String getTitle(Context context) {
- if (mTitle != null) return mTitle;
- return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
- }
-
- /**
- * Set {@link Uri} to be used for ringtone playback.
- * {@link IRingtonePlayer}.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public void setUri(Uri uri) {
- setUri(uri, null);
- }
-
- /**
- * @hide
- */
- public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) {
- mVolumeShaperConfig = volumeShaperConfig;
- }
-
- /**
- * Set {@link Uri} to be used for ringtone playback. Attempts to open
- * locally, otherwise will delegate playback to remote
- * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required.
- *
- * @hide
- */
- public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
- mVolumeShaperConfig = volumeShaperConfig;
- mUri = uri;
- if (mUri == null) {
- destroyLocalPlayer();
- }
- }
-
- /** {@hide} */
- @UnsupportedAppUsage
- public Uri getUri() {
- return mUri;
- }
-
- /**
- * Plays the ringtone.
- */
- public void play() {
- if (mLocalPlayer != null) {
- // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
- // (typically because ringer mode is vibrate).
- if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
- != 0) {
- startLocalPlayer();
- } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
- // is haptic only ringtone
- startLocalPlayer();
- }
- } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
- final Uri canonicalUri = mUri.getCanonicalUri();
- final boolean looping;
- final float volume;
- synchronized (mPlaybackSettingsLock) {
- looping = mIsLooping;
- volume = mVolume;
- }
- try {
- mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes,
- volume, looping, mVolumeShaperConfig);
- } catch (RemoteException e) {
- if (!playFallbackRingtone()) {
- Log.w(TAG, "Problem playing ringtone: " + e);
- }
- }
- } else {
- if (!playFallbackRingtone()) {
- Log.w(TAG, "Neither local nor remote playback available");
- }
- }
- }
-
- /**
- * Stops a playing ringtone.
- */
- public void stop() {
- if (mLocalPlayer != null) {
- destroyLocalPlayer();
- } else if (mAllowRemote && (mRemotePlayer != null)) {
- try {
- mRemotePlayer.stop(mRemoteToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem stopping ringtone: " + e);
- }
- }
- }
-
- private void destroyLocalPlayer() {
- if (mLocalPlayer != null) {
- if (mHapticGenerator != null) {
- mHapticGenerator.release();
- mHapticGenerator = null;
- }
- mLocalPlayer.setOnCompletionListener(null);
- mLocalPlayer.reset();
- mLocalPlayer.release();
- mLocalPlayer = null;
- mVolumeShaper = null;
- synchronized (sActiveRingtones) {
- sActiveRingtones.remove(this);
- }
- }
- }
-
- private void startLocalPlayer() {
- if (mLocalPlayer == null) {
- return;
- }
- synchronized (sActiveRingtones) {
- sActiveRingtones.add(this);
- }
- if (LOGD) {
- Log.d(TAG, "Starting ringtone playback");
- }
- mLocalPlayer.setOnCompletionListener(mCompletionListener);
- mLocalPlayer.start();
- if (mVolumeShaper != null) {
- mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
- }
- }
-
- /**
- * Whether this ringtone is currently playing.
- *
- * @return True if playing, false otherwise.
- */
- public boolean isPlaying() {
- if (mLocalPlayer != null) {
- return mLocalPlayer.isPlaying();
- } else if (mAllowRemote && (mRemotePlayer != null)) {
- try {
- return mRemotePlayer.isPlaying(mRemoteToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem checking ringtone: " + e);
- return false;
- }
- } else {
- Log.w(TAG, "Neither local nor remote playback available");
- return false;
- }
- }
-
- private boolean playFallbackRingtone() {
- int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
- if (mAudioManager.getStreamVolume(streamType) == 0) {
- return false;
- }
- int ringtoneType = RingtoneManager.getDefaultType(mUri);
- if (ringtoneType != -1 &&
- RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
- Log.w(TAG, "not playing fallback for " + mUri);
- return false;
- }
- // Default ringtone, try fallback ringtone.
- try {
- AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
- com.android.internal.R.raw.fallbackring);
- if (afd == null) {
- Log.e(TAG, "Could not load fallback ringtone");
- return false;
- }
- mLocalPlayer = new MediaPlayer();
- if (afd.getDeclaredLength() < 0) {
- mLocalPlayer.setDataSource(afd.getFileDescriptor());
- } else {
- mLocalPlayer.setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- mLocalPlayer.setAudioAttributes(mAudioAttributes);
- synchronized (mPlaybackSettingsLock) {
- applyPlaybackProperties_sync();
- }
- if (mVolumeShaperConfig != null) {
- mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
- }
- mLocalPlayer.prepare();
- startLocalPlayer();
- afd.close();
- } catch (IOException ioe) {
- destroyLocalPlayer();
- Log.e(TAG, "Failed to open fallback ringtone");
- return false;
- } catch (NotFoundException nfe) {
- Log.e(TAG, "Fallback ringtone does not exist");
- return false;
- }
- return true;
- }
-
- public boolean getPreferBuiltinDevice() {
- return mPreferBuiltinDevice;
- }
-
- public VolumeShaper.Configuration getVolumeShaperConfig() {
- return mVolumeShaperConfig;
- }
-
- public boolean isLocalOnly() {
- return mAllowRemote;
- }
-
- public boolean isUsingRemotePlayer() {
- // V2 testing api, but this is the v1 approximation.
- return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
- }
-
- class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
- @Override
- public void onCompletion(MediaPlayer mp) {
- synchronized (sActiveRingtones) {
- sActiveRingtones.remove(RingtoneV1.this);
- }
- mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
- }
- }
-}
diff --git a/media/java/android/media/RingtoneV2.java b/media/java/android/media/RingtoneV2.java
deleted file mode 100644
index f1a8155..0000000
--- a/media/java/android/media/RingtoneV2.java
+++ /dev/null
@@ -1,690 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources.NotFoundException;
-import android.media.Ringtone.Injectables;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * New Ringtone implementation, supporting vibration as well as sound, and configuration via a
- * builder. During flagged transition, the original implementation is in RingtoneV1.java.
- *
- * Only modified methods are moved here.
- *
- * @hide
- */
-class RingtoneV2 implements Ringtone.ApiInterface {
- private static final String TAG = "RingtoneV2";
-
- /**
- * The ringtone should only play sound. Any vibration is managed externally.
- * @hide
- */
- public static final int MEDIA_SOUND = 1;
- /**
- * The ringtone should only play vibration. Any sound is managed externally.
- * Requires the {@link android.Manifest.permission#VIBRATE} permission.
- * @hide
- */
- public static final int MEDIA_VIBRATION = 1 << 1;
- /**
- * The ringtone should play sound and vibration.
- * @hide
- */
- public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION;
-
- // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be
- // safe if new media types were added.
- static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION;
-
- /**
- * Declares the types of media that this Ringtone is allowed to play.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "MEDIA_", value = {
- MEDIA_SOUND,
- MEDIA_VIBRATION,
- MEDIA_SOUND_AND_VIBRATION,
- })
- public @interface RingtoneMedia {}
-
- private static final String[] MEDIA_COLUMNS = new String[] {
- MediaStore.Audio.Media._ID,
- MediaStore.Audio.Media.TITLE
- };
- /** Selection that limits query results to just audio files */
- private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
- + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
-
- private final Context mContext;
- private final Vibrator mVibrator;
- private final AudioManager mAudioManager;
- private VolumeShaper.Configuration mVolumeShaperConfig;
-
- /**
- * Flag indicating if we're allowed to fall back to remote playback using
- * {@link #mRemoteRingtoneService}. Typically this is false when we're the remote
- * player and there is nobody else to delegate to.
- */
- private final boolean mAllowRemote;
- private final IRingtonePlayer mRemoteRingtoneService;
- private final Injectables mInjectables;
-
- private final int mEnabledMedia;
-
- private final Uri mUri;
- private String mTitle;
-
- private AudioAttributes mAudioAttributes;
- private boolean mUseExactAudioAttributes;
- private boolean mPreferBuiltinDevice;
- private RingtonePlayer mActivePlayer;
- // playback properties, use synchronized with mPlaybackSettingsLock
- private boolean mIsLooping;
- private float mVolume;
- private boolean mHapticGeneratorEnabled;
- private final Object mPlaybackSettingsLock = new Object();
- private final VibrationEffect mVibrationEffect;
-
- /** Only for use by Ringtone constructor */
- RingtoneV2(@NonNull Context context, @NonNull Injectables injectables,
- boolean allowRemote, @Ringtone.RingtoneMedia int enabledMedia,
- @Nullable Uri uri, @NonNull AudioAttributes audioAttributes,
- boolean useExactAudioAttributes,
- @Nullable VolumeShaper.Configuration volumeShaperConfig,
- boolean preferBuiltinDevice, float soundVolume, boolean looping,
- boolean hapticGeneratorEnabled, @Nullable VibrationEffect vibrationEffect) {
- // Context
- mContext = context;
- mInjectables = injectables;
- mVibrator = mContext.getSystemService(Vibrator.class);
- mAudioManager = mContext.getSystemService(AudioManager.class);
- mRemoteRingtoneService = allowRemote ? mAudioManager.getRingtonePlayer() : null;
- mAllowRemote = (mRemoteRingtoneService != null); // Only set if allowed, and present.
-
- // Properties potentially propagated to remote player.
- mEnabledMedia = enabledMedia;
- mUri = uri;
- mAudioAttributes = audioAttributes;
- mUseExactAudioAttributes = useExactAudioAttributes;
- mVolumeShaperConfig = volumeShaperConfig;
- mPreferBuiltinDevice = preferBuiltinDevice; // system-only, not supported for remote play.
- mVolume = soundVolume;
- mIsLooping = looping;
- mHapticGeneratorEnabled = hapticGeneratorEnabled;
- mVibrationEffect = vibrationEffect;
- }
-
- /** @hide */
- @RingtoneMedia
- public int getEnabledMedia() {
- return mEnabledMedia;
- }
-
- /**
- * Sets the stream type where this ringtone will be played.
- *
- * @param streamType The stream, see {@link AudioManager}.
- * @deprecated use {@link #setAudioAttributes(AudioAttributes)}
- */
- @Deprecated
- public void setStreamType(int streamType) {
- setAudioAttributes(
- getAudioAttributesForLegacyStreamType(streamType, "setStreamType()"));
- }
-
- private AudioAttributes getAudioAttributesForLegacyStreamType(int streamType, String originOp) {
- PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", originOp);
- return new AudioAttributes.Builder()
- .setInternalLegacyStreamType(streamType)
- .build();
- }
-
- /**
- * Gets the stream type where this ringtone will be played.
- *
- * @return The stream type, see {@link AudioManager}.
- * @deprecated use of stream types is deprecated, see
- * {@link #setAudioAttributes(AudioAttributes)}
- */
- @Deprecated
- public int getStreamType() {
- return AudioAttributes.toLegacyStreamType(mAudioAttributes);
- }
-
- /**
- * Sets the {@link AudioAttributes} for this ringtone.
- * @param attributes the non-null attributes characterizing this ringtone.
- */
- public void setAudioAttributes(AudioAttributes attributes)
- throws IllegalArgumentException {
- // TODO: deprecate this method - it will be done with a builder.
- if (attributes == null) {
- throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
- }
- mAudioAttributes = attributes;
- // Setting the audio attributes requires re-initializing the player.
- if (mActivePlayer != null) {
- // The audio attributes have to be set before the media player is prepared.
- // Re-initialize it.
- reinitializeActivePlayer();
- }
- }
-
- /**
- * Returns the vibration effect that this ringtone was created with, if vibration is enabled.
- * Otherwise, returns null.
- * @hide
- */
- @Nullable
- public VibrationEffect getVibrationEffect() {
- return mVibrationEffect;
- }
-
- /** @hide */
- @VisibleForTesting
- public boolean getPreferBuiltinDevice() {
- return mPreferBuiltinDevice;
- }
-
- /** @hide */
- @VisibleForTesting
- public VolumeShaper.Configuration getVolumeShaperConfig() {
- return mVolumeShaperConfig;
- }
-
- /**
- * Returns whether this player is local only, or can defer to the remote player. The
- * result may differ from the builder if there is no remote player available at all.
- * @hide
- */
- @VisibleForTesting
- public boolean isLocalOnly() {
- return !mAllowRemote;
- }
-
- /** @hide */
- @VisibleForTesting
- public boolean isUsingRemotePlayer() {
- return mActivePlayer instanceof RemoteRingtonePlayer;
- }
-
- /**
- * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
- * the one on which outgoing audio for SIM calls is played.
- *
- * @param audioManager the audio manage.
- * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
- * none can be found.
- */
- private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
- AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
- for (AudioDeviceInfo device : deviceList) {
- if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- return device;
- }
- }
- return null;
- }
-
- /**
- * Creates a local media player for the ringtone using currently set attributes.
- * @return true if media player creation succeeded or is deferred,
- * false if it did not succeed and can't be tried remotely.
- * @hide
- */
- public boolean reinitializeActivePlayer() {
- // Try creating a local media player, or fallback to creating a remote one.
- Trace.beginSection("reinitializeActivePlayer");
- try {
- if (mActivePlayer != null) {
- // This would only happen if calling the deprecated setAudioAttributes after
- // building the Ringtone.
- stopAndReleaseActivePlayer();
- }
-
- boolean vibrationOnly = (mEnabledMedia & MEDIA_ALL) == MEDIA_VIBRATION;
- // Vibration can come from the audio file if using haptic generator or if haptic
- // channels are a possibility.
- boolean maybeAudioVibration = mUri != null && mInjectables.isHapticPlaybackSupported()
- && (mHapticGeneratorEnabled || !mAudioAttributes.areHapticChannelsMuted());
-
- // VibrationEffect only, use the simplified player without checking for haptic channels.
- if (vibrationOnly && !maybeAudioVibration && mVibrationEffect != null) {
- mActivePlayer = new LocalRingtonePlayer.VibrationEffectPlayer(
- mVibrationEffect, mAudioAttributes, mVibrator, mIsLooping);
- return true;
- }
-
- AudioDeviceInfo preferredDevice =
- mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
- if (mUri != null) {
- mActivePlayer = LocalRingtonePlayer.create(mContext, mAudioManager, mVibrator, mUri,
- mAudioAttributes, vibrationOnly, mVibrationEffect, mInjectables,
- mVolumeShaperConfig, preferredDevice, mHapticGeneratorEnabled, mIsLooping,
- mVolume);
- } else {
- // Using the remote player won't help play a null Uri. Revert straight to fallback.
- // The vibration-only case was already covered above.
- mActivePlayer = createFallbackRingtonePlayer();
- // Fall through to attempting remote fallback play if null.
- }
-
- if (mActivePlayer == null && mAllowRemote) {
- mActivePlayer = new RemoteRingtonePlayer(mRemoteRingtoneService, mUri,
- mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
- mVolumeShaperConfig, mHapticGeneratorEnabled, mIsLooping, mVolume);
- }
-
- return mActivePlayer != null;
- } finally {
- if (mActivePlayer != null) {
- Log.d(TAG, "Initialized ringtone player with " + mActivePlayer.getClass());
- } else {
- Log.d(TAG, "Failed to initialize ringtone player");
- }
- Trace.endSection();
- }
- }
-
- @Nullable
- private LocalRingtonePlayer createFallbackRingtonePlayer() {
- int ringtoneType = RingtoneManager.getDefaultType(mUri);
- if (ringtoneType != -1
- && RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
- Log.w(TAG, "not playing fallback for " + mUri);
- return null;
- }
- // Default ringtone, try fallback ringtone.
- try (AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
- com.android.internal.R.raw.fallbackring)) {
- if (afd == null) {
- Log.e(TAG, "Could not load fallback ringtone");
- return null;
- }
-
- AudioDeviceInfo preferredDevice =
- mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
- return LocalRingtonePlayer.createForFallback(mAudioManager, mVibrator, afd,
- mAudioAttributes, mVibrationEffect, mInjectables, mVolumeShaperConfig,
- preferredDevice, mIsLooping, mVolume);
- } catch (NotFoundException nfe) {
- Log.e(TAG, "Fallback ringtone does not exist");
- return null;
- } catch (IOException e) {
- // As with the above messages, not including much information about the
- // failure so as not to expose details of the fallback ringtone resource.
- Log.e(TAG, "Exception reading fallback ringtone");
- return null;
- }
- }
-
- /**
- * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
- * @hide
- */
- public boolean hasHapticChannels() {
- return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels();
- }
-
- /**
- * Returns the {@link AudioAttributes} used by this object.
- * @return the {@link AudioAttributes} that were set with
- * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
- */
- public AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
- }
-
- /**
- * Sets the player to be looping or non-looping.
- * @param looping whether to loop or not.
- */
- public void setLooping(boolean looping) {
- synchronized (mPlaybackSettingsLock) {
- mIsLooping = looping;
- if (mActivePlayer != null) {
- mActivePlayer.setLooping(looping);
- }
- }
- }
-
- /**
- * Returns whether the looping mode was enabled on this player.
- * @return true if this player loops when playing.
- */
- public boolean isLooping() {
- synchronized (mPlaybackSettingsLock) {
- return mIsLooping;
- }
- }
-
- /**
- * Sets the volume on this player.
- * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
- * corresponds to no attenuation being applied.
- */
- public void setVolume(float volume) {
- // Ignore if sound not enabled.
- if ((mEnabledMedia & MEDIA_SOUND) == 0) {
- return;
- }
- if (volume < 0.0f) {
- volume = 0.0f;
- } else if (volume > 1.0f) {
- volume = 1.0f;
- }
-
- synchronized (mPlaybackSettingsLock) {
- mVolume = volume;
- if (mActivePlayer != null) {
- mActivePlayer.setVolume(volume);
- }
- }
- }
-
- /**
- * Returns the volume scalar set on this player.
- * @return a value between 0.0f and 1.0f.
- */
- public float getVolume() {
- synchronized (mPlaybackSettingsLock) {
- return mVolume;
- }
- }
-
- /**
- * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
- * only be enabled on devices that support the effect.
- *
- * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
- * @see android.media.audiofx.HapticGenerator#isAvailable()
- */
- public boolean setHapticGeneratorEnabled(boolean enabled) {
- if (!mInjectables.isHapticGeneratorAvailable()) {
- return false;
- }
- synchronized (mPlaybackSettingsLock) {
- mHapticGeneratorEnabled = enabled;
- if (mActivePlayer != null) {
- mActivePlayer.setHapticGeneratorEnabled(enabled);
- }
- }
- return true;
- }
-
- /**
- * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
- * @return true if the HapticGenerator is enabled.
- */
- public boolean isHapticGeneratorEnabled() {
- synchronized (mPlaybackSettingsLock) {
- return mHapticGeneratorEnabled;
- }
- }
-
- /**
- * Returns a human-presentable title for ringtone. Looks in media
- * content provider. If not in either, uses the filename
- *
- * @param context A context used for querying.
- */
- public String getTitle(Context context) {
- if (mTitle != null) return mTitle;
- return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
- }
-
-
- /** {@hide} */
- @UnsupportedAppUsage
- public Uri getUri() {
- return mUri;
- }
-
- /**
- * Plays the ringtone.
- */
- public void play() {
- if (mActivePlayer != null) {
- Log.d(TAG, "Starting ringtone playback");
- if (mActivePlayer.play()) {
- return;
- } else {
- // Discard active player: play() is only meant to be called once.
- stopAndReleaseActivePlayer();
- }
- }
- if (!playFallbackRingtone()) {
- Log.w(TAG, "Neither local nor remote playback available");
- }
- }
-
- /**
- * Stops a playing ringtone.
- */
- public void stop() {
- stopAndReleaseActivePlayer();
- }
-
- private void stopAndReleaseActivePlayer() {
- if (mActivePlayer != null) {
- mActivePlayer.stopAndRelease();
- mActivePlayer = null;
- }
- }
-
- /**
- * Whether this ringtone is currently playing.
- *
- * @return True if playing, false otherwise.
- */
- public boolean isPlaying() {
- if (mActivePlayer != null) {
- return mActivePlayer.isPlaying();
- } else {
- Log.w(TAG, "No active ringtone player");
- return false;
- }
- }
-
- /**
- * Fallback during the play stage rather than initialization, typically due to an issue
- * communicating with the remote player.
- */
- private boolean playFallbackRingtone() {
- if (mActivePlayer != null) {
- Log.wtf(TAG, "Playing fallback ringtone with another active player");
- stopAndReleaseActivePlayer();
- }
- int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
- if (mAudioManager.getStreamVolume(streamType) == 0) {
- // TODO: Return true? If volume is off, this is a successful play.
- return false;
- }
- mActivePlayer = createFallbackRingtonePlayer();
- if (mActivePlayer == null) {
- return false; // the create method logs if it returns null.
- } else if (mActivePlayer.play()) {
- return true;
- } else {
- stopAndReleaseActivePlayer();
- return false;
- }
- }
-
- void setTitle(String title) {
- mTitle = title;
- }
-
- /**
- * Play a specific ringtone. This interface is implemented by either local (this process) or
- * proxied-remote playback via AudioManager.getRingtonePlayer, so that the caller
- * (Ringtone class) can just use a single player after the initial creation.
- * @hide
- */
- interface RingtonePlayer {
- /**
- * Start playing the ringtone, returning false if there was a problem that
- * requires falling back to the fallback ringtone resource.
- */
- boolean play();
- boolean isPlaying();
- void stopAndRelease();
-
- // Mutating playback methods.
- void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo);
- void setLooping(boolean looping);
- void setHapticGeneratorEnabled(boolean enabled);
- void setVolume(float volume);
-
- boolean hasHapticChannels();
- }
-
- /**
- * Remote RingtonePlayer. All operations are delegated via the IRingtonePlayer interface, which
- * should ultimately be backed by a RingtoneLocalPlayer within the system services.
- */
- static class RemoteRingtonePlayer implements RingtonePlayer {
- private final IBinder mRemoteToken = new Binder();
- private final IRingtonePlayer mRemoteRingtoneService;
- private final Uri mCanonicalUri;
- private final int mEnabledMedia;
- private final VibrationEffect mVibrationEffect;
- private final VolumeShaper.Configuration mVolumeShaperConfig;
- private final AudioAttributes mAudioAttributes;
- private final boolean mUseExactAudioAttributes;
- private boolean mIsLooping;
- private float mVolume;
- private boolean mHapticGeneratorEnabled;
-
- RemoteRingtonePlayer(@NonNull IRingtonePlayer remoteRingtoneService,
- @NonNull Uri uri, @NonNull AudioAttributes audioAttributes,
- boolean useExactAudioAttributes,
- @RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
- @Nullable VolumeShaper.Configuration volumeShaperConfig,
- boolean hapticGeneratorEnabled, boolean initialIsLooping, float initialVolume) {
- mRemoteRingtoneService = remoteRingtoneService;
- mCanonicalUri = (uri == null) ? null : uri.getCanonicalUri();
- mAudioAttributes = audioAttributes;
- mUseExactAudioAttributes = useExactAudioAttributes;
- mEnabledMedia = enabledMedia;
- mVibrationEffect = vibrationEffect;
- mVolumeShaperConfig = volumeShaperConfig;
- mHapticGeneratorEnabled = hapticGeneratorEnabled;
- mIsLooping = initialIsLooping;
- mVolume = initialVolume;
- }
-
- @Override
- public boolean play() {
- try {
- mRemoteRingtoneService.playRemoteRingtone(mRemoteToken, mCanonicalUri,
- mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
- mVolume, mIsLooping, mHapticGeneratorEnabled, mVolumeShaperConfig);
- return true;
- } catch (RemoteException e) {
- Log.w(TAG, "Problem playing ringtone: " + e);
- return false;
- }
- }
-
- @Override
- public boolean isPlaying() {
- try {
- return mRemoteRingtoneService.isPlaying(mRemoteToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem checking ringtone isPlaying: " + e);
- return false;
- }
- }
-
- @Override
- public void stopAndRelease() {
- try {
- mRemoteRingtoneService.stop(mRemoteToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem stopping ringtone: " + e);
- }
- }
-
- @Override
- public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
- // un-implemented for remote (but not used outside system).
- }
-
- @Override
- public void setLooping(boolean looping) {
- mIsLooping = looping;
- try {
- mRemoteRingtoneService.setLooping(mRemoteToken, looping);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem setting looping: " + e);
- }
- }
-
- @Override
- public void setHapticGeneratorEnabled(boolean enabled) {
- mHapticGeneratorEnabled = enabled;
- try {
- mRemoteRingtoneService.setHapticGeneratorEnabled(mRemoteToken, enabled);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem setting hapticGeneratorEnabled: " + e);
- }
- }
-
- @Override
- public void setVolume(float volume) {
- mVolume = volume;
- try {
- mRemoteRingtoneService.setVolume(mRemoteToken, volume);
- } catch (RemoteException e) {
- Log.w(TAG, "Problem setting volume: " + e);
- }
- }
-
- @Override
- public boolean hasHapticChannels() {
- // FIXME: support remote player, or internalize haptic channels support and remove
- // entirely.
- return false;
- }
- }
-
-}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index b85decc..bbe461c 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -58,6 +58,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -597,6 +598,21 @@
setRegistration(null);
}
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @FlaggedApi(Flags.FLAG_AUDIO_MIX_TEST_API)
+ public List<AudioMix> getMixes() {
+ if (!Flags.audioMixTestApi()) {
+ return Collections.emptyList();
+ }
+ synchronized (mLock) {
+ return List.copyOf(mConfig.getMixes());
+ }
+ }
+
public void setRegistration(String regId) {
synchronized (mLock) {
mRegistrationId = regId;
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
index 19d9f00..8c008bc 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -18,6 +18,9 @@
import android.os.Bundle;
+import java.util.Collections;
+import java.util.List;
+
/**
* @hide
*/
@@ -75,4 +78,29 @@
}
return false;
}
+
+ /**
+ * Returns a paged version of the given {@code list}, using the paging parameters in {@code
+ * options}.
+ */
+ public static List<MediaBrowser.MediaItem> applyPagingOptions(
+ List<MediaBrowser.MediaItem> list, final Bundle options) {
+ if (list == null) {
+ return null;
+ }
+ int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ if (page == -1 && pageSize == -1) {
+ return list;
+ }
+ int fromIndex = pageSize * page;
+ int toIndex = fromIndex + pageSize;
+ if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+ return Collections.EMPTY_LIST;
+ }
+ if (toIndex > list.size()) {
+ toIndex = list.size();
+ }
+ return list.subList(fromIndex, toIndex);
+ }
}
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 694756c..eb980a3 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -16,9 +16,11 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -51,14 +53,14 @@
/**
* Request option: one-way
* <p> With this option, no response is expected after sending the request.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public static final int REQUEST_OPTION_ONEWAY = 2;
/**
* Request option: one-shot
* <p> With this option, only one response will be given per request.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public static final int REQUEST_OPTION_ONESHOT = 3;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 672f58b..aed3e60e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -556,85 +556,85 @@
/**
* Informs the application that the session has been tuned to the given channel.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_CHANNEL_URI
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TUNED = "tuned";
/**
* Sends the type and ID of a selected track. This is used to inform the application that a
* specific track is selected.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_TRACK_TYPE
* @see SESSION_DATA_KEY_TRACK_ID
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
/**
* Sends the list of all audio/video/subtitle tracks.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_TRACKS
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
/**
* Informs the application that the video is now available for watching.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
/**
* Informs the application that the video became unavailable for some reason.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
/**
* Notifies response for broadcast info.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
"broadcast_info_response";
/**
* Notifies response for advertisement.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_RESPONSE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
/**
* Notifies the advertisement buffer is consumed.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_BUFFER
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
/**
* Sends the TV message.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see TvInputService.Session#notifyTvMessage(int, Bundle)
* @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
@@ -657,9 +657,9 @@
*
* <p> Type: android.net.Uri
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
/**
@@ -671,9 +671,9 @@
* <p> Type: Integer
*
* @see TvTrackInfo#getType()
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
/**
@@ -682,9 +682,9 @@
* <p> Type: String
*
* @see TvTrackInfo#getId()
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
/**
@@ -692,9 +692,9 @@
*
* <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACKS = "tracks";
/**
@@ -704,9 +704,9 @@
*
* <p> Type: Integer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
"video_unavailable_reason";
@@ -715,9 +715,9 @@
*
* <p> Type: android.media.tv.BroadcastInfoResponse
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
/**
@@ -725,9 +725,9 @@
*
* <p> Type: android.media.tv.AdResponse
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
/**
@@ -735,9 +735,9 @@
*
* <p> Type: android.media.tv.AdBuffer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
/**
@@ -747,9 +747,9 @@
*
* <p> Type: Integer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6b03041..6658918 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -768,12 +768,12 @@
/**
* Informs the application that the video freeze state has been updated.
*
- * When {@code true}, the video is frozen on the last frame but audio playback remains
+ * <p>When {@code true}, the video is frozen on the last frame but audio playback remains
* active.
*
* @param isFrozen Whether or not the video is frozen
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void notifyVideoFreezeUpdated(boolean isFrozen) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@@ -1262,7 +1262,7 @@
}
/**
- * Notifies data related to this session to corresponding linked
+ * Sends data related to this session to corresponding linked
* {@link android.media.tv.ad.TvAdService} object via TvAdView.
*
* <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
@@ -1272,21 +1272,21 @@
*
* @param type data type
* @param data the related data values
- * @hide
*/
- public void notifyTvInputSessionData(
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+ public void sendTvInputSessionData(
@NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+ if (DEBUG) Log.d(TAG, "sendTvInputSessionData");
if (mSessionCallback != null) {
mSessionCallback.onTvInputSessionData(type, data);
}
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTvInputSessionData", e);
+ Log.w(TAG, "error in sendTvInputSessionData", e);
}
}
});
@@ -1441,10 +1441,10 @@
*
* @param type the type of the data
* @param data a bundle contains the data received
- * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see android.media.tv.ad.TvAdView#setTvView(TvView)
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public void onTvAdSessionData(
@NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ffc121e..e604cb7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -683,13 +683,15 @@
* Sets whether or not the video is frozen. While the video is frozen, audio playback will
* continue.
*
- * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+ * <p>This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
* received with the command to freeze the video.
*
- * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+ * <p>This will freeze the video to the last frame when the state is set to {@code true}.
+ *
+ * @see TvView.TvInputCallback#setVideoFrozen(boolean)
* @param isFrozen whether or not the video is frozen.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void setVideoFrozen(boolean isFrozen) {
if (mSession != null) {
mSession.setVideoFrozen(isFrozen);
@@ -1325,6 +1327,16 @@
public void onTvMessage(@NonNull String inputId,
@TvInputManager.TvMessageType int type, @NonNull Bundle data) {
}
+
+ /**
+ * This is called when the video freeze status is updated.
+ *
+ * @see #setVideoFrozen(boolean)
+ * @param inputId The ID of the TV input bound to this view.
+ * @param isFrozen Whether or not the video is currently frozen on the las
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onVideoFreezeUpdated(@NonNull String inputId, boolean isFrozen) {}
}
/**
@@ -1753,5 +1765,19 @@
mCallback.onTvMessage(mInputId, type, data);
}
}
+
+ @Override
+ public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoFreezeUpdated(isFrozen=" + isFrozen + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onVideoFreezeUpdated - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onVideoFreezeUpdated(mInputId, isFrozen);
+ }
+ }
}
}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index f373bed..59b10c6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -68,7 +68,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
@@ -77,7 +76,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
@@ -86,7 +84,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
@@ -95,7 +92,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
@@ -104,7 +100,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_BACK_URI = "back_uri";
@@ -112,7 +107,6 @@
* Broadcast intent action to send app command to TV app.
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String ACTION_APP_LINK_COMMAND =
"android.media.tv.ad.action.APP_LINK_COMMAND";
@@ -123,7 +117,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
@@ -134,7 +127,6 @@
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
* @see TvAdServiceInfo#getId()
- * @hide
*/
public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
@@ -144,7 +136,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
@@ -155,7 +146,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
@@ -171,36 +161,32 @@
/**
* Sends an advertisement request to be processed by the related TV input.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_REQUEST
- * @hide
*/
public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
/**
* Notifies the advertisement buffer is ready.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_BUFFER
- * @hide
*/
public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
/**
* Sends request for broadcast info.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
- * @hide
*/
public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
/**
* Removes request for broadcast info.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
- * @hide
*/
public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
"remove_broadcast_info_request";
@@ -220,8 +206,7 @@
*
* <p> Type: android.media.tv.AdRequest
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
@@ -230,8 +215,7 @@
*
* <p> Type: android.media.tv.AdBuffer
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
@@ -240,8 +224,7 @@
*
* <p> Type: android.media.tv.BroadcastInfoRequest
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
@@ -250,8 +233,7 @@
*
* <p> Type: Integer
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
@@ -494,8 +476,17 @@
*
* @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
* in {@link TvAdServiceInfo#getId()}.
- * @param command The command to be sent.
- * @hide
+ * @param command The command to be sent. The command is a bundle with the following keys:
+ * <ul>
+ * <li>{@link #APP_LINK_KEY_PACKAGE_NAME}: The package name of the app to be
+ * launched.
+ * <li>{@link #APP_LINK_KEY_CLASS_NAME}: The class name of the app to be
+ * launched.
+ * <li>{@link #APP_LINK_KEY_COMMAND_TYPE}: The command type.
+ * <li>{@link #APP_LINK_KEY_SERVICE_ID}: The ID of the TV AD service.
+ * <li>{@link #APP_LINK_KEY_BACK_URI}: The URI to be used to return to the
+ * previous app.
+ * </ul>
*/
public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
try {
@@ -510,7 +501,6 @@
*
* @param callback A callback used to monitor status of the TV AD services.
* @param executor A {@link Executor} that the status change will be delivered to.
- * @hide
*/
public void registerCallback(
@CallbackExecutor @NonNull Executor executor,
@@ -526,7 +516,6 @@
* Unregisters the existing {@link TvAdServiceCallback}.
*
* @param callback The existing callback to remove.
- * @hide
*/
public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 953b5cf..6c8a8fd 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -136,7 +136,6 @@
* Called when app link command is received.
*
* @see TvAdManager#sendAppLinkCommand(String, Bundle)
- * @hide
*/
public void onAppLinkCommand(@NonNull Bundle command) {
}
@@ -199,7 +198,6 @@
*
* @param enable {@code true} if you want to enable the media view. {@code false}
* otherwise.
- * @hide
*/
@CallSuper
public void setMediaViewEnabled(final boolean enable) {
@@ -225,7 +223,6 @@
* Returns {@code true} if media view is enabled, {@code false} otherwise.
*
* @see #setMediaViewEnabled(boolean)
- * @hide
*/
public boolean isMediaViewEnabled() {
return mMediaViewEnabled;
@@ -253,21 +250,18 @@
/**
* Starts TvAdService session.
- * @hide
*/
public void onStartAdService() {
}
/**
* Stops TvAdService session.
- * @hide
*/
public void onStopAdService() {
}
/**
* Resets TvAdService session.
- * @hide
*/
public void onResetAdService() {
}
@@ -618,9 +612,8 @@
*
* @param type the type of the data
* @param data a bundle contains the data received
- * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see android.media.tv.ad.TvAdView#setTvView(TvView)
- * @hide
*/
public void onTvInputSessionData(
@NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
@@ -636,7 +629,6 @@
*
* @param width The width of the media view, in pixels.
* @param height The height of the media view, in pixels.
- * @hide
*/
public void onMediaViewSizeChanged(@Px int width, @Px int height) {
}
@@ -646,7 +638,6 @@
* implementation can override this method and return its own view.
*
* @return a view attached to the media window. {@code null} if no media view is created.
- * @hide
*/
@Nullable
public View onCreateMediaView() {
@@ -654,27 +645,26 @@
}
/**
- * Notifies data related to this session to corresponding linked
+ * Sends data related to this session to corresponding linked
* {@link android.media.tv.TvInputService} object via TvView.
*
* @param type data type
* @param data the related data values
* @see TvAdView#setTvView(TvView)
- * @hide
*/
- public void notifyTvAdSessionData(
+ public void sendTvAdSessionData(
@NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+ if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
if (mSessionCallback != null) {
mSessionCallback.onTvAdSessionData(type, data);
}
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTvAdSessionData", e);
+ Log.w(TAG, "error in sendTvAdSessionData", e);
}
}
});
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index be88506..ee01468 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -160,7 +160,6 @@
* @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
* to unlink the TvView.
* @return {@code true} if it's linked successfully; {@code false} otherwise.
- * @hide
*/
public boolean setTvView(@Nullable TvView tvView) {
if (tvView == null) {
@@ -259,7 +258,6 @@
* Resets this TvAdView to release its resources.
*
* <p>It can be reused by call {@link #prepareAdService(String, String)}.
- * @hide
*/
public void reset() {
if (DEBUG) Log.d(TAG, "reset()");
@@ -362,7 +360,6 @@
*
* @param event The input event.
* @return {@code true} if the event was handled by the view, {@code false} otherwise.
- * @hide
*/
public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
if (mOnUnhandledInputEventListener != null) {
@@ -381,7 +378,6 @@
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to be
* handled by the next receiver, return {@code false}.
- * @hide
*/
public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
return false;
@@ -392,7 +388,6 @@
* by the TV AD service.
*
* @param listener The callback to be invoked when the unhandled input event is received.
- * @hide
*/
public void setOnUnhandledInputEventListener(
@NonNull @CallbackExecutor Executor executor,
@@ -407,7 +402,6 @@
*
* @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
* @see #clearOnUnhandledInputEventListener()
- * @hide
*/
@Nullable
public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
@@ -416,7 +410,6 @@
/**
* Clears the {@link OnUnhandledInputEventListener}.
- * @hide
*/
public void clearOnUnhandledInputEventListener() {
mOnUnhandledInputEventListener = null;
@@ -453,7 +446,6 @@
/**
* Starts the AD service.
- * @hide
*/
public void startAdService() {
if (DEBUG) {
@@ -466,7 +458,6 @@
/**
* Stops the AD service.
- * @hide
*/
public void stopAdService() {
if (DEBUG) {
@@ -481,7 +472,6 @@
* Resets the AD service.
*
* <p>This releases the resources of the corresponding {@link TvAdService.Session}.
- * @hide
*/
public void resetAdService() {
if (DEBUG) {
@@ -622,7 +612,6 @@
/**
* Interface definition for a callback to be invoked when the unhandled input event is received.
- * @hide
*/
public interface OnUnhandledInputEventListener {
/**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 498eec6..7cf32ec 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -2634,8 +2634,8 @@
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
- * called.
+ * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+ * is called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
* @param signingId the ID to identify the request.
@@ -2644,7 +2644,6 @@
* @param host The host of the SSL CLient Authentication Server
* @param port The port of the SSL Client Authentication Server
* @param data the original bytes to be signed.
- * @hide
*/
public void onRequestSigning(
Session session, String signingId, String algorithm, String host,
@@ -2657,7 +2656,6 @@
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
- * @hide
*/
public void onRequestCertificate(Session session, String host, int port) {
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 6b0620c..eba26d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -742,8 +742,8 @@
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
* @param cert the SSL certificate received.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
}
@@ -896,13 +896,13 @@
}
/**
- * Called when video becomes frozen or unfrozen. Audio playback will continue while
- * video will be frozen to the last frame if {@code true}.
+ * Called when video becomes frozen or unfrozen. Audio playback will continue while video
+ * will be frozen to the last frame if {@code true}.
+ *
* @param isFrozen Whether or not the video is frozen.
- * @hide
*/
- public void onVideoFreezeUpdated(boolean isFrozen) {
- }
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onVideoFreezeUpdated(boolean isFrozen) {}
/**
* Called when content is allowed.
@@ -1666,9 +1666,9 @@
* @see #onSigningResult(String, byte[])
* @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
* @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
- * @hide
*/
@CallSuper
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@NonNull String host, int port, @NonNull byte[] data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1695,8 +1695,9 @@
*
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
- * @hide
*/
+ @CallSuper
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void requestCertificate(@NonNull String host, int port) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 584ea84..29a3b98 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -723,12 +723,12 @@
}
/**
- * Alerts the TV Interactive app that the video freeze state has been updated.
- * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true},
+ * the video is frozen on the last frame while audio playback continues.
*
* @param isFrozen Whether the video is frozen.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void notifyVideoFreezeUpdated(boolean isFrozen) {
if (DEBUG) {
Log.d(TAG, "notifyVideoFreezeUpdated");
@@ -760,12 +760,12 @@
}
/**
- * Send the requested SSL certificate to the TV Interactive App
+ * Sends the requested SSL certificate to the TV Interactive App
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
* @param cert the SSL certificate requested
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
if (DEBUG) {
Log.d(TAG, "sendCertificate");
@@ -1390,6 +1390,37 @@
}
/**
+ * This is called when
+ * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+ * is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param host The hostname of the SSL authentication server.
+ * @param port The port of the SSL authentication server.
+ * @param data the original bytes to be signed.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
+ @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param host The hostname of the SSL authentication server.
+ * @param port The port of the SSL authentication server.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host,
+ int port) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String,
* TvRecordingInfo)} is called.
*
@@ -1957,5 +1988,34 @@
mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
}
}
+
+ @Override
+ public void onRequestSigning(
+ Session session, String id, String algorithm, String host, int port, byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSigning");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSigning - session not created");
+ return;
+ }
+ if (mCallback != null && Flags.tiafVApis()) {
+ mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data);
+ }
+ }
+
+ @Override
+ public void onRequestCertificate(Session session, String host, int port) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCertificate");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCertificate - session not created");
+ return;
+ }
+ if (mCallback != null && Flags.tiafVApis()) {
+ mCallback.onRequestCertificate(mIAppServiceId, host, port);
+ }
+ }
}
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index e8ef464..ba7ab9a 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -48,7 +48,6 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -728,7 +727,7 @@
List<MediaBrowser.MediaItem> filteredList =
(flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
- ? applyOptions(list, options) : list;
+ ? MediaBrowserUtils.applyPagingOptions(list, options) : list;
final ParceledListSlice<MediaBrowser.MediaItem> pls;
if (filteredList == null) {
pls = null;
@@ -762,27 +761,6 @@
}
}
- private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list,
- final Bundle options) {
- if (list == null) {
- return null;
- }
- int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
- int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
- if (page == -1 && pageSize == -1) {
- return list;
- }
- int fromIndex = pageSize * page;
- int toIndex = fromIndex + pageSize;
- if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
- return Collections.EMPTY_LIST;
- }
- if (toIndex > list.size()) {
- toIndex = list.size();
- }
- return list.subList(fromIndex, toIndex);
- }
-
private void performLoadItem(String itemId, final ConnectionRecord connection,
final ResultReceiver receiver) {
final Result<MediaBrowser.MediaItem> result =
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index f105ae9..236b1fd 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -45,8 +45,10 @@
@RunWith(AndroidJUnit4.class)
@RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfigurationUnitTest {
- private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
- private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+ private static final long DEFAULT_FADE_OUT_DURATION_MS =
+ FadeManagerConfiguration.getDefaultFadeOutDurationMillis();
+ private static final long DEFAULT_FADE_IN_DURATION_MS =
+ FadeManagerConfiguration.getDefaultFadeInDurationMillis();
private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
private static final long TEST_FADE_IN_DURATION_MS = 750;
private static final int TEST_INVALID_USAGE = -10;
@@ -259,16 +261,6 @@
}
@Test
- public void testSetFadeState_toEnableAuto() {
- final int fadeStateAuto = FadeManagerConfiguration.FADE_STATE_ENABLED_AUTO;
- FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
- .setFadeState(fadeStateAuto).build();
-
- expect.withMessage("Fade state when enabled for audio").that(fmc.getFadeState())
- .isEqualTo(fadeStateAuto);
- }
-
- @Test
public void testSetFadeState_toInvalid_fails() {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
new FadeManagerConfiguration.Builder()
@@ -310,13 +302,13 @@
}
@Test
- public void testSetFadeVolShaperConfig_withNullVolumeShaper_getsNull() {
+ public void testSetFadeOutVolShaperConfig_withNullVolumeShaper_getsNull() {
FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(mFmc)
.setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
/* VolumeShaper.Configuration= */ null)
.setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
/* VolumeShaper.Configuration= */ null)
- .clearFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
+ .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
expect.withMessage("Fade out volume shaper config set with null value")
.that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
@@ -547,31 +539,13 @@
}
@Test
- public void testClearFadeableUsage() {
- final int usageToClear = AudioAttributes.USAGE_MEDIA;
- List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
- updatedUsages.remove((Integer) usageToClear);
+ public void testClearFadeableUsages() {
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(mFmc)
+ .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .build();
- FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
- .Builder(mFmc).clearFadeableUsage(usageToClear).build();
-
- expect.withMessage("Clear fadeable usage").that(updatedFmc.getFadeableUsages())
- .containsExactlyElementsIn(updatedUsages);
- }
-
- @Test
- public void testClearFadeableUsage_withInvalidUsage_fails() {
- FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
-
- IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
- fmcBuilder.clearFadeableUsage(TEST_INVALID_USAGE)
- );
-
- FadeManagerConfiguration fmc = fmcBuilder.build();
- expect.withMessage("Clear invalid usage").that(thrown).hasMessageThat()
- .contains("Invalid usage");
- expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
- .containsExactlyElementsIn(mFmc.getFadeableUsages());
+ expect.withMessage("Clear fadeable usages").that(updatedFmc.getFadeableUsages())
+ .containsExactlyElementsIn(List.of(AudioAttributes.USAGE_VOICE_COMMUNICATION));
}
@Test
@@ -673,7 +647,7 @@
}
@Test
- public void testClearUnfadeableContentType() {
+ public void testClearUnfadeableContentTypes() {
List<Integer> unfadeableContentTypes = new ArrayList<>(Arrays.asList(
AudioAttributes.CONTENT_TYPE_MOVIE,
AudioAttributes.CONTENT_TYPE_SONIFICATION
@@ -682,23 +656,10 @@
FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder()
.setUnfadeableContentTypes(unfadeableContentTypes)
- .clearUnfadeableContentType(contentTypeToClear).build();
+ .clearUnfadeableContentTypes().build();
- unfadeableContentTypes.remove((Integer) contentTypeToClear);
expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
- .containsExactlyElementsIn(unfadeableContentTypes);
- }
-
- @Test
- public void testClearUnfadeableContentType_withInvalidContentType_fails() {
- FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
-
- IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
- fmcBuilder.clearUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
- );
-
- expect.withMessage("Invalid content type exception").that(thrown).hasMessageThat()
- .contains("Invalid content type");
+ .isEmpty();
}
@Test
@@ -735,7 +696,7 @@
}
@Test
- public void testClearUnfadebaleUid() {
+ public void testClearUnfadebaleUids() {
final List<Integer> unfadeableUids = List.of(
TEST_UID_1,
TEST_UID_2
@@ -744,10 +705,9 @@
.setUnfadeableUids(unfadeableUids).build();
FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(fmc)
- .clearUnfadeableUid(TEST_UID_1).build();
+ .clearUnfadeableUids().build();
- expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids())
- .isEqualTo(List.of(TEST_UID_2));
+ expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids()).isEmpty();
}
@Test
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 7a329bc..1325fc1 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -22,7 +22,6 @@
"android-ex-camera2",
"android.media.playback.flags-aconfig-java",
"flag-junit",
- "testables",
"testng",
"truth",
],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
deleted file mode 100644
index 6d5f82c..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtone
-per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
deleted file mode 100644
index 3c0c684..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ /dev/null
@@ -1,840 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mediaframeworktest.unit;
-
-import static android.media.Ringtone.MEDIA_SOUND;
-import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
-import static android.media.Ringtone.MEDIA_VIBRATION;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.IRingtonePlayer;
-import android.media.MediaPlayer;
-import android.media.Ringtone;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.mediaframeworktest.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
-
- private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
-
- private static final AudioAttributes RINGTONE_ATTRIBUTES =
- audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
- private static final AudioAttributes RINGTONE_ATTRIBUTES_WITH_HC =
- new AudioAttributes.Builder(RINGTONE_ATTRIBUTES).setHapticChannelsMuted(false).build();
- private static final VibrationAttributes RINGTONE_VIB_ATTRIBUTES =
- new VibrationAttributes.Builder(RINGTONE_ATTRIBUTES).build();
-
- private static final VibrationEffect VIBRATION_EFFECT =
- VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
- private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
- VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
-
- @Rule
- public final RingtoneInjectablesTrackingTestRule
- mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
-
- @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
- @Mock private IRingtonePlayer mMockRemotePlayer;
- @Mock private Vibrator mMockVibrator;
- private AudioManager mSpyAudioManager;
- private TestableContext mContext;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- TestableContext testContext =
- new TestableContext(InstrumentationRegistry.getTargetContext(), null);
- testContext.getTestablePermissions().setPermission(Manifest.permission.VIBRATE,
- PackageManager.PERMISSION_GRANTED);
- AudioManager realAudioManager = testContext.getSystemService(AudioManager.class);
- mSpyAudioManager = spy(realAudioManager);
- when(mSpyAudioManager.getRingtonePlayer()).thenReturn(mMockRemotePlayer);
- testContext.addMockSystemService(AudioManager.class, mSpyAudioManager);
- testContext.addMockSystemService(Vibrator.class, mMockVibrator);
-
- mContext = spy(testContext);
- }
-
- @Test
- public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES).setUri(SOUND_URI).build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
- assertThat(ringtone.getVolume()).isEqualTo(1.0f);
- assertThat(ringtone.isLooping()).isEqualTo(false);
- assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
- assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
- assertThat(ringtone.getVolumeShaperConfig()).isNull();
- assertThat(ringtone.isLocalOnly()).isFalse();
-
- // Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
- verify(mockMediaPlayer).setVolume(1.0f);
- verify(mockMediaPlayer).setLooping(false);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
-
- // Verify dynamic controls.
- ringtone.setVolume(0.8f);
- verify(mockMediaPlayer).setVolume(0.8f);
- when(mockMediaPlayer.isLooping()).thenReturn(false);
- ringtone.setLooping(true);
- verify(mockMediaPlayer).isLooping();
- verify(mockMediaPlayer).setLooping(true);
- HapticGenerator mockHapticGenerator =
- mMediaPlayerRule.expectHapticGenerator(mockMediaPlayer);
- ringtone.setHapticGeneratorEnabled(true);
- verify(mockHapticGenerator).setEnabled(true);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
-
- // This test is intended to strictly verify all interactions with MediaPlayer in a local
- // playback case. This shouldn't be necessary in other tests that have the same basic
- // setup.
- verifyNoMoreInteractions(mockMediaPlayer);
- verify(mockHapticGenerator).release();
- verifyNoMoreInteractions(mockHapticGenerator);
- verifyZeroInteractions(mMockRemotePlayer);
- verifyZeroInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaPlayerWithAudioCoupledOverride() throws Exception {
- // Audio coupled playback is enabled in the incoming attributes, plus an instruction
- // to leave the attributes alone. This test verifies that the attributes reach the
- // media player without changing.
- final AudioAttributes audioAttributes = RINGTONE_ATTRIBUTES_WITH_HC;
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND, audioAttributes)
- .setUri(SOUND_URI)
- .setUseExactAudioAttributes(true)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
-
- // Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
-
- verifyZeroInteractions(mMockRemotePlayer);
- verifyZeroInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_fullLifecycleUsingRemoteMediaPlayer() throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- setupFileNotFound(mockMediaPlayer, SOUND_URI);
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
- assertThat(ringtone.getVolume()).isEqualTo(1.0f);
- assertThat(ringtone.isLooping()).isEqualTo(false);
- assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
- assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
- assertThat(ringtone.getVolumeShaperConfig()).isNull();
- assertThat(ringtone.isLocalOnly()).isFalse();
-
- // Initialization did try to create a local media player.
- verify(mockMediaPlayer).setDataSource(mContext, SOUND_URI);
- // setDataSource throws file not found, so nothing else will happen on the local player.
- verify(mockMediaPlayer).release();
-
- // Delegates to remote media player.
- ringtone.play();
- verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), eq(SOUND_URI),
- eq(RINGTONE_ATTRIBUTES), eq(false), eq(MEDIA_SOUND), isNull(),
- eq(1.0f), eq(false), eq(false), isNull());
- IBinder remoteToken = mIBinderCaptor.getValue();
-
- // Verify dynamic controls.
- ringtone.setVolume(0.8f);
- verify(mMockRemotePlayer).setVolume(remoteToken, 0.8f);
- ringtone.setLooping(true);
- verify(mMockRemotePlayer).setLooping(remoteToken, true);
- ringtone.setHapticGeneratorEnabled(true);
- verify(mMockRemotePlayer).setHapticGeneratorEnabled(remoteToken, true);
-
- ringtone.stop();
- verify(mMockRemotePlayer).stop(remoteToken);
- verifyNoMoreInteractions(mMockRemotePlayer);
- verifyNoMoreInteractions(mockMediaPlayer);
- verifyZeroInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaWithVibration() throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Prepare
- // Uses attributes with haptic channels enabled, but will use the effect when there aren't
- // any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
- verify(mockMediaPlayer).setVolume(1.0f);
- verify(mockMediaPlayer).setLooping(false);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
-
- verifyLocalPlay(mockMediaPlayer);
- verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
- // Verify dynamic controls.
- ringtone.setVolume(0.8f);
- verify(mockMediaPlayer).setVolume(0.8f);
-
- // Set looping doesn't affect an already-started vibration.
- when(mockMediaPlayer.isLooping()).thenReturn(false); // Checks original
- ringtone.setLooping(true);
- verify(mockMediaPlayer).isLooping();
- verify(mockMediaPlayer).setLooping(true);
-
- // This is ignored because there's a vibration effect being used.
- ringtone.setHapticGeneratorEnabled(true);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
- verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
- // This test is intended to strictly verify all interactions with MediaPlayer in a local
- // playback case. This shouldn't be necessary in other tests that have the same basic
- // setup.
- verifyNoMoreInteractions(mockMediaPlayer);
- verifyZeroInteractions(mMockRemotePlayer);
- verifyNoMoreInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaWithVibrationOnly() throws Exception {
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- Ringtone ringtone =
- newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
- // TODO: set sound uri too in diff test
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
- assertThat(ringtone.getUri()).isNull();
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Play
- ringtone.play();
-
- verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
- // Verify dynamic controls (no-op without sound)
- ringtone.setVolume(0.8f);
-
- // Set looping doesn't affect an already-started vibration.
- ringtone.setLooping(true);
-
- // This is ignored because there's a vibration effect being used and no sound.
- ringtone.setHapticGeneratorEnabled(true);
-
- // Release
- ringtone.stop();
- verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
- // This test is intended to strictly verify all interactions with MediaPlayer in a local
- // playback case. This shouldn't be necessary in other tests that have the same basic
- // setup.
- verifyZeroInteractions(mMockRemotePlayer);
- verifyNoMoreInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaWithVibrationOnlyAndSoundUriNoHapticChannels()
- throws Exception {
- // A media player will still be created for vibration-only because the vibration can come
- // from haptic channels on the sound file (although in this case it doesn't).
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- Ringtone ringtone =
- newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Prepare
- // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
- // knows there aren't any.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
- verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
- verify(mockMediaPlayer).setLooping(false);
- verify(mockMediaPlayer).prepare();
- verify(mockMediaPlayer).release(); // abandoned: no haptic channels.
-
- // Play
- ringtone.play();
-
- verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
- // Verify dynamic controls (no-op without sound)
- ringtone.setVolume(0.8f);
-
- // Set looping doesn't affect an already-started vibration.
- ringtone.setLooping(true);
-
- // This is ignored because there's a vibration effect being used and no sound.
- ringtone.setHapticGeneratorEnabled(true);
-
- // Release
- ringtone.stop();
- verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
- // This test is intended to strictly verify all interactions with MediaPlayer in a local
- // playback case. This shouldn't be necessary in other tests that have the same basic
- // setup.
- verifyZeroInteractions(mMockRemotePlayer);
- verifyNoMoreInteractions(mMockVibrator);
- verifyNoMoreInteractions(mockMediaPlayer);
- }
-
- @Test
- public void testRingtone_localMediaWithVibrationOnlyAndSoundUriWithHapticChannels()
- throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
- Ringtone ringtone =
- newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Prepare
- // Uses attributes with haptic channels enabled, but will use the effect when there aren't
- // any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
- verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
- verify(mockMediaPlayer).setLooping(false);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- // Vibrator.vibrate isn't called because the vibration comes from the sound.
- verifyLocalPlay(mockMediaPlayer);
-
- // Verify dynamic controls (no-op without sound)
- ringtone.setVolume(0.8f);
-
- when(mockMediaPlayer.isLooping()).thenReturn(false); // Checks original
- ringtone.setLooping(true);
- verify(mockMediaPlayer).isLooping();
- verify(mockMediaPlayer).setLooping(true);
-
- // This is ignored because it's using haptic channels.
- ringtone.setHapticGeneratorEnabled(true);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
-
- // This test is intended to strictly verify all interactions with MediaPlayer in a local
- // playback case. This shouldn't be necessary in other tests that have the same basic
- // setup.
- verifyZeroInteractions(mMockRemotePlayer);
- verifyZeroInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaWithVibrationPrefersHapticChannels() throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Prepare
- // The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- when(mockMediaPlayer.isPlaying()).thenReturn(true);
- verifyLocalPlay(mockMediaPlayer);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
-
- verifyZeroInteractions(mMockRemotePlayer);
- // Nothing after the initial hasVibrator - it uses audio-coupled.
- verifyNoMoreInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_localMediaWithVibrationButSoundMuted() throws Exception {
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
- doReturn(0).when(mSpyAudioManager)
- .getStreamVolume(AudioAttributes.toLegacyStreamType(RINGTONE_ATTRIBUTES));
- when(mMockVibrator.hasVibrator()).thenReturn(true);
- Ringtone ringtone =
- newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .setVibrationEffect(VIBRATION_EFFECT)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
- verify(mMockVibrator).hasVibrator();
-
- // Verify all the properties.
- assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
- assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
- assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
- // Prepare
- // The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- // The media player is never played, because sound is muted.
- verify(mockMediaPlayer, never()).start();
- when(mockMediaPlayer.isPlaying()).thenReturn(true);
- verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
- // Release
- ringtone.stop();
- verify(mockMediaPlayer).release();
- verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
- verifyZeroInteractions(mMockRemotePlayer);
- // Nothing after the initial hasVibrator - it uses audio-coupled.
- verifyNoMoreInteractions(mMockVibrator);
- }
-
- @Test
- public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
- AssetFileDescriptor testResourceFd =
- mContext.getResources().openRawResourceFd(R.raw.shortmp3);
- // Ensure it will flow as expected.
- assertThat(testResourceFd).isNotNull();
- assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
- mContext.getOrCreateTestableResources()
- .addOverride(com.android.internal.R.raw.fallbackring, testResourceFd);
-
- MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
- Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(null)
- .build();
- assertThat(ringtone).isNotNull();
- assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
- // Delegates straight to fallback in local player.
- // Prepare
- verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
- verify(mockMediaPlayer).setVolume(1.0f);
- verify(mockMediaPlayer).setLooping(false);
- verify(mockMediaPlayer).prepare();
-
- // Play
- ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
-
- // Release
- ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
-
- verifyNoMoreInteractions(mockMediaPlayer);
- verifyNoMoreInteractions(mMockRemotePlayer);
- }
-
- @Test
- public void testRingtone_nullMediaOnBuilderUsesFallbackViaRemote() throws Exception {
- mContext.getOrCreateTestableResources()
- .addOverride(com.android.internal.R.raw.fallbackring, null);
- Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(null)
- .setLooping(true) // distinct from haptic generator, to match plumbing
- .build();
- assertThat(ringtone).isNotNull();
- // Local player fallback fails as the resource isn't found (no media player creation is
- // attempted), and then goes on to create the remote player.
- assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
- ringtone.play();
- verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), isNull(),
- eq(RINGTONE_ATTRIBUTES), eq(false),
- eq(MEDIA_SOUND), isNull(),
- eq(1.0f), eq(true), eq(false), isNull());
- ringtone.stop();
- verify(mMockRemotePlayer).stop(mIBinderCaptor.getValue());
- verifyNoMoreInteractions(mMockRemotePlayer);
- }
-
- @Test
- public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
- mContext.getOrCreateTestableResources()
- .addOverride(com.android.internal.R.raw.fallbackring, null);
- Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(null)
- .setLocalOnly()
- .build();
- // Local player fallback fails as the resource isn't found (no media player creation is
- // attempted), and since there is no local player, the ringtone ends up having nothing to
- // do.
- assertThat(ringtone).isNull();
- }
-
- private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
- AudioAttributes audioAttributes) {
- return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
- .setInjectables(mMediaPlayerRule.injectables);
- }
-
- private static AudioAttributes audioAttributes(int audioUsage) {
- return new AudioAttributes.Builder()
- .setUsage(audioUsage)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build();
- }
-
- /** Makes the mock get some sort of file access problem. */
- private void setupFileNotFound(MediaPlayer mockMediaPlayer, Uri uri) throws Exception {
- doThrow(new FileNotFoundException("Fake file not found"))
- .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
- }
-
- private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
- AudioAttributes expectedAudioAttributes) throws Exception {
- verify(mockPlayer).setDataSource(mContext, expectedUri);
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
- AudioAttributes expectedAudioAttributes) throws Exception {
- // This is very specific but it's a simple way to test that the test resource matches.
- if (afd.getDeclaredLength() < 0) {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor());
- } else {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).setOnCompletionListener(any());
- verify(mockMediaPlayer).start();
- }
-
- private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).stop();
- verify(mockMediaPlayer).setOnCompletionListener(isNull());
- verify(mockMediaPlayer).reset();
- verify(mockMediaPlayer).release();
- }
-
- /**
- * This rule ensures that all expected media player creations from the factory do actually
- * occur. The reason for this level of control is that creating a media player is fairly
- * expensive and blocking, so we do want unit tests of this class to "declare" interactions
- * of all created media players.
- *
- * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
- * failed (and media player assertions may just be a distracting side effect). Otherwise, the
- * teardown failures hide the real test ones.
- */
- public static class RingtoneInjectablesTrackingTestRule implements TestRule {
- public Ringtone.Injectables injectables = new TestInjectables();
- public boolean hapticGeneratorAvailable = true;
-
- // Queue of (local) media players, in order of expected creation. Enqueue using
- // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
- // This queue is asserted to be empty at the end of the test.
- private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
- // Similar to media players, but for haptic generator, which also needs releasing.
- private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
- // Media players with haptic channels.
- private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- base.evaluate();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
- .that(mMockMediaPlayerQueue).isEmpty();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage(
- "Test setup an expectLocalHapticGenerator but it wasn't consumed")
- .that(mMockHapticGeneratorMap).isEmpty();
- }
- };
- }
-
- private TestMediaPlayer expectLocalMediaPlayer() {
- TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
- // Delegate to simulated methods. This means they can be verified but also reflect
- // realistic transitions from the TestMediaPlayer.
- doCallRealMethod().when(mockMediaPlayer).start();
- doCallRealMethod().when(mockMediaPlayer).stop();
- doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- mMockMediaPlayerQueue.add(mockMediaPlayer);
- return mockMediaPlayer;
- }
-
- private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
- HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
- // A test should never want this.
- assertWithMessage("Can't expect a second haptic generator created "
- + "for one media player")
- .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
- .isNull();
- return mockHapticGenerator;
- }
-
- private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
- if (hasHapticChannels) {
- mHapticChannels.add(mp);
- } else {
- mHapticChannels.remove(mp);
- }
- }
-
- private class TestInjectables extends Ringtone.Injectables {
- @Override
- public MediaPlayer newMediaPlayer() {
- assertWithMessage(
- "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
- .that(mMockMediaPlayerQueue)
- .isNotEmpty();
- return mMockMediaPlayerQueue.remove();
- }
-
- @Override
- public boolean isHapticGeneratorAvailable() {
- return hapticGeneratorAvailable;
- }
-
- @Override
- public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
- HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
- assertWithMessage("Unexpected HapticGenerator creation. "
- + "Bug or need expectHapticGenerator")
- .that(mockHapticGenerator)
- .isNotNull();
- return mockHapticGenerator;
- }
-
- @Override
- public boolean isHapticPlaybackSupported() {
- return true;
- }
-
- @Override
- public boolean hasHapticChannels(MediaPlayer mp) {
- return mHapticChannels.contains(mp);
- }
- }
- }
-
- /**
- * MediaPlayer relies on a native backend and so its necessary to intercept calls from
- * fake usage hitting them.
- *
- * Mocks don't work directly on native calls, but if they're overridden then it does work.
- * Some basic state faking is also done to make the mocks more realistic.
- */
- private static class TestMediaPlayer extends MediaPlayer {
- private boolean mIsPlaying = false;
- private boolean mIsLooping = false;
-
- @Override
- public void start() {
- mIsPlaying = true;
- }
-
- @Override
- public void stop() {
- mIsPlaying = false;
- }
-
- @Override
- public void setLooping(boolean value) {
- mIsLooping = value;
- }
-
- @Override
- public boolean isLooping() {
- return mIsLooping;
- }
-
- @Override
- public boolean isPlaying() {
- return mIsPlaying;
- }
-
- void simulatePlayingFinished() {
- if (!mIsPlaying) {
- throw new IllegalStateException(
- "Attempted to pretend playing finished when not playing");
- }
- mIsPlaying = false;
- }
- }
-}
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
deleted file mode 100644
index 55b98c4..0000000
--- a/media/tests/ringtone/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-package {
- // See: http://go/android-license-faq
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "MediaRingtoneTests",
-
- srcs: ["src/**/*.java"],
-
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
-
- static_libs: [
- "androidx.test.rules",
- "testng",
- "androidx.test.ext.truth",
- "frameworks-base-testutils",
- ],
-
- test_suites: [
- "device-tests",
- "automotive-tests",
- ],
-
- platform_apis: true,
- certificate: "platform",
-}
diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml
deleted file mode 100644
index 27eda07..0000000
--- a/media/tests/ringtone/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.framework.base.media.ringtone.tests">
-
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_USERS" />
-
- <application android:debuggable="true">
- <uses-library android:name="android.test.runner" />
-
- <activity android:name="MediaRingtoneTests"
- android:label="Media Ringtone Tests"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.framework.base.media.ringtone.tests"
- android:label="Media Ringtone Tests"/>
-</manifest>
diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING
deleted file mode 100644
index 6f25c14..0000000
--- a/media/tests/ringtone/TEST_MAPPING
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "presubmit": [
- {
- "name": "MediaRingtoneTests",
- "options": [
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
- ],
- "postsubmit": [
- {
- "name": "MediaRingtoneTests",
- "options": [
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv
deleted file mode 100644
index d6eba1a..0000000
--- a/media/tests/ringtone/res/raw/test_haptic_file.ahv
+++ /dev/null
@@ -1,17 +0,0 @@
-<vibration-effect>
- <waveform-effect>
- <waveform-entry durationMs="63" amplitude="255"/>
- <waveform-entry durationMs="63" amplitude="231"/>
- <waveform-entry durationMs="63" amplitude="208"/>
- <waveform-entry durationMs="63" amplitude="185"/>
- <waveform-entry durationMs="63" amplitude="162"/>
- <waveform-entry durationMs="63" amplitude="139"/>
- <waveform-entry durationMs="63" amplitude="115"/>
- <waveform-entry durationMs="63" amplitude="92"/>
- <waveform-entry durationMs="63" amplitude="69"/>
- <waveform-entry durationMs="63" amplitude="46"/>
- <waveform-entry durationMs="63" amplitude="23"/>
- <waveform-entry durationMs="63" amplitude="0"/>
- <waveform-entry durationMs="1250" amplitude="0"/>
- </waveform-effect>
-</vibration-effect>
diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3
deleted file mode 100644
index c1b2fdf..0000000
--- a/media/tests/ringtone/res/raw/test_sound_file.mp3
+++ /dev/null
Binary files differ
diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
deleted file mode 100644
index a92b298..0000000
--- a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import static com.google.android.mms.ContentType.AUDIO_MP3;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.os.vibrator.persistence.VibrationXmlParser;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.framework.base.media.ringtone.tests.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class RingtoneManagerTest {
- @RingtoneManager.MediaType
- private final int mMediaType;
- private final List<Uri> mAddedFilesUri;
- private Context mContext;
- private RingtoneManager mRingtoneManager;
- private long mTimestamp;
-
- @Parameterized.Parameters(name = "media = {0}")
- public static Iterable<?> data() {
- return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION);
- }
-
- public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) {
- mMediaType = mediaType;
- mAddedFilesUri = new ArrayList<>();
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- mTimestamp = SystemClock.uptimeMillis();
- mRingtoneManager = new RingtoneManager(mContext);
- mRingtoneManager.setMediaType(mMediaType);
- }
-
- @After
- public void tearDown() {
- // Clean up media store
- for (Uri fileUri : mAddedFilesUri) {
- mContext.getContentResolver().delete(fileUri, null);
- }
- }
-
- @Test
- public void testSetMediaType_withValidValue_setsMediaCorrectly() {
- mRingtoneManager.setMediaType(mMediaType);
- assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType);
- }
-
- @Test
- public void testSetMediaType_withInvalidValue_throwsException() {
- assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999));
- }
-
- @Test
- public void testSetMediaType_afterCallingGetCursor_throwsException() {
- mRingtoneManager.getCursor();
- assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType));
- }
-
- @Test
- public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception {
- String fileName = generateUniqueFileName("new_file");
- Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
- assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName);
- }
-
- @Test
- public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception {
- //TODO(b/261571543) Remove this assumption once we support playing vibrations.
- assumeTrue(mMediaType == Ringtone.MEDIA_SOUND);
- String fileName = generateUniqueFileName("new_file");
- Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
- ringtone.play();
- assertThat(ringtone.isPlaying()).isTrue();
-
- ringtone.stop();
- assertThat(ringtone.isPlaying()).isFalse();
- }
-
- @Test
- public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception {
- RingtoneManager audioRingtoneManager = new RingtoneManager(mContext);
- String audioFileName = generateUniqueFileName("ringtone");
- addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName);
-
- RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext);
- vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION);
- String vibrationFileName = generateUniqueFileName("vibration");
- addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName);
-
- Cursor audioCursor = audioRingtoneManager.getCursor();
- Cursor vibrationCursor = vibrationRingtoneManager.getCursor();
-
- List<String> audioTitles = extractRecordTitles(audioCursor);
- List<String> vibrationTitles = extractRecordTitles(vibrationCursor);
-
- assertThat(audioTitles).contains(audioFileName);
- assertThat(audioTitles).doesNotContain(vibrationFileName);
-
- assertThat(vibrationTitles).contains(vibrationFileName);
- assertThat(vibrationTitles).doesNotContain(audioFileName);
- }
-
- private List<String> extractRecordTitles(Cursor cursor) {
- List<String> titles = new ArrayList<>();
-
- if (cursor.moveToFirst()) {
- do {
- String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
- titles.add(title);
- } while (cursor.moveToNext());
- }
-
- return titles;
- }
-
- private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName)
- throws Exception {
- Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile(
- fileName) : addVibrationFile(fileName);
- mAddedFilesUri.add(fileUri);
-
- int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri);
- Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition);
- // Validate this is the expected ringtone.
- assertThat(ringtone.getUri()).isEqualTo(fileUri);
- return ringtone;
- }
-
- private Uri addAudioFile(String fileName) throws Exception {
- ContentResolver resolver = mContext.getContentResolver();
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3");
- contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES);
- contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3);
- contentValues.put(MediaStore.Audio.Media.TITLE, fileName);
- contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1);
-
- Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- contentValues);
- writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file);
-
- return resolver.canonicalizeOrElse(contentUri);
- }
-
- private Uri addVibrationFile(String fileName) throws Exception {
- ContentResolver resolver = mContext.getContentResolver();
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv");
- contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH,
- Environment.DIRECTORY_DOWNLOADS);
- contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE,
- VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE);
- contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName);
-
- Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore
- .VOLUME_EXTERNAL), contentValues);
- writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file);
-
- return resolver.canonicalizeOrElse(contentUri);
- }
-
- private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource)
- throws Exception {
- try (ParcelFileDescriptor pfd =
- resolver.openFileDescriptor(contentUri, "w", null)) {
- InputStream inputStream = mContext.getResources().openRawResource(rawResource);
- FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor());
- outputStream.write(inputStream.readAllBytes());
-
- inputStream.close();
- outputStream.flush();
- outputStream.close();
-
- } catch (Exception e) {
- throw new Exception("Failed to write data to file", e);
- }
- }
-
- private String generateUniqueFileName(String prefix) {
- return TextUtils.formatSimple("%s_%d", prefix, mTimestamp);
- }
-
-}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 28cf250..845a8f9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -232,13 +232,13 @@
method public final void sendResponseApdu(byte[]);
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 7cd2533..89b0322 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,11 +244,11 @@
public static final String KEY_DATA = "data";
/**
- * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+ * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
* polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
/**
* POLLING_LOOP_TYPE_A is the value associated with the key
@@ -299,33 +299,33 @@
public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
/**
- * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
- * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+ * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
- * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+ * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
/**
* @hide
*/
- public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+ public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
"android.nfc.cardemulation.POLLING_FRAMES";
/**
@@ -405,7 +405,7 @@
break;
case MSG_POLLING_LOOP:
ArrayList<Bundle> frames =
- msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+ msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
Bundle.class);
processPollingFrames(frames);
break;
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index fdda9ea..910ff96 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index c7c2fda..4bf0e99 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 53852cb..b47a4dc 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -26,6 +26,6 @@
<dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
- <integer name="autofill_max_visible_datasets">3</integer>
- <dimen name="dropdown_touch_target_min_width">48dp</dimen>
+ <integer name="autofill_max_visible_datasets">5</integer>
+ <dimen name="dropdown_touch_target_min_height">48dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 9a2cf61..e7d1072 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -40,7 +40,7 @@
Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this")
}
if (showCancel) {
- val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName)
+ val appLabel = packageManager.appLabel(cancelUiRequest.packageName)
if (appLabel == null) {
Log.d(TAG, "Received UI cancel request with an invalid package name.")
null
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0ccb07a..3097387 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,6 +58,7 @@
private val providerEnabledList: List<ProviderData>
private val providerDisabledList: List<DisabledProviderData>?
val resultReceiver: ResultReceiver?
+ val finalResponseReceiver: ResultReceiver?
var initialUiState: UiState
@@ -105,6 +106,11 @@
ResultReceiver::class.java
)
+ finalResponseReceiver = intent.getParcelableExtra(
+ Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver::class.java
+ )
+
isReqForAllOptions = intent.getBooleanExtra(
Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
/*defaultValue=*/ false
@@ -113,7 +119,7 @@
val cancellationRequest = getCancelUiRequest(intent)
val cancelUiRequestState = cancellationRequest?.let {
- CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
+ CancelUiRequestState(getAppLabel(context.getPackageManager(), it.packageName))
}
initialUiState = when (requestInfo?.type) {
@@ -200,7 +206,7 @@
}
fun onCancel(cancelCode: Int) {
- sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
+ sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
}
fun onOptionSelected(
@@ -219,6 +225,10 @@
)
val resultDataBundle = Bundle()
UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
+
+ resultDataBundle.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ finalResponseReceiver)
+
resultReceiver?.send(
BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
resultDataBundle
@@ -286,10 +296,14 @@
fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
- resultReceiver: ResultReceiver?
+ resultReceiver: ResultReceiver?,
+ finalResponseReceiver: ResultReceiver?
) {
if (requestToken != null && resultReceiver != null) {
val resultData = Bundle()
+ resultData.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ finalResponseReceiver)
+
BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
resultReceiver.send(cancelCode, resultData)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 05aa548..4771237 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -135,7 +135,7 @@
Log.d(
Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
" ui = $shouldShowCancellationUi")
- val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName)
+ val appDisplayName = getAppLabel(packageManager, cancelUiRequest.packageName)
if (!shouldShowCancellationUi) {
this.finish()
}
@@ -216,13 +216,18 @@
android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
+ val finalResponseResultReceiver = intent.getParcelableExtra(
+ android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver::class.java
+ )
+
val requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
)
CredentialManagerRepo.sendCancellationCode(
BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
- requestInfo?.token, resultReceiver
+ requestInfo?.token, resultReceiver, finalResponseResultReceiver
)
this.finish()
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 6c5a984..f4da1e6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -72,7 +72,7 @@
init {
uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT,
- credManRepo.requestInfo?.appPackageName)
+ credManRepo.requestInfo?.packageName)
}
/**************************************************************************/
@@ -107,7 +107,7 @@
if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) {
this.uiMetrics.resetInstanceId()
this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST,
- credManRepo.requestInfo?.appPackageName)
+ credManRepo.requestInfo?.packageName)
}
}
@@ -189,7 +189,7 @@
private fun onInternalError() {
Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR,
- credManRepo.requestInfo?.appPackageName)
+ credManRepo.requestInfo?.packageName)
credManRepo.onParsingFailureCancel()
uiState = uiState.copy(dialogState = DialogState.COMPLETE)
}
@@ -399,6 +399,6 @@
@Composable
fun logUiEvent(uiEventEnum: UiEventEnum) {
- this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.appPackageName)
+ this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 64595e2..997c45e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -195,7 +195,7 @@
}
return com.android.credentialmanager.getflow.RequestDisplayInfo(
appName = originName?.ifEmpty { null }
- ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
+ ?: getAppLabel(context.packageManager, requestInfo.packageName)
?: return null,
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
preferIdentityDocUi = getCredentialRequest.data.getBoolean(
@@ -269,7 +269,7 @@
return null
}
val appLabel = originName?.ifEmpty { null }
- ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
+ ?: getAppLabel(context.packageManager, requestInfo.packageName)
?: return null
val createCredentialRequest = requestInfo.createCredentialRequest ?: return null
val createCredentialRequestJetpack = CreateCredentialRequest.createFrom(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index f496c1f..8fde5d7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -19,13 +19,12 @@
import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
-import android.credentials.Credential
+import android.content.Intent
import android.credentials.CredentialManager
-import android.credentials.CredentialOption
-import android.credentials.GetCandidateCredentialsException
-import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.CredentialOption
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
@@ -47,7 +46,6 @@
import android.service.credentials.CredentialProviderService
import android.util.Log
import android.view.autofill.AutofillId
-import android.view.autofill.AutofillValue
import android.view.autofill.IAutoFillManagerClient
import android.widget.RemoteViews
import android.widget.inline.InlinePresentationSpec
@@ -131,30 +129,7 @@
val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
GetCandidateCredentialsException> {
override fun onResult(result: GetCandidateCredentialsResponse) {
- Log.i(TAG, "getCandidateCredentials onResponse")
-
- if (result.getCredentialResponse != null) {
- val autofillId: AutofillId? = result.getCredentialResponse
- .credential.data.getParcelable(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- AutofillId::class.java)
- Log.i(TAG, "getCandidateCredentials final response, autofillId: " +
- autofillId)
-
- if (autofillId != null) {
- autofillCallback.autofill(
- sessionId,
- mutableListOf(autofillId),
- mutableListOf(
- AutofillValue.forText(
- convertResponseToJson(result.getCredentialResponse)
- )
- ),
- false)
- }
- return
- }
-
+ Log.i(TAG, "getCandidateCredentials onResult")
val fillResponse = convertToFillResponse(result, request,
responseClientState)
if (fillResponse != null) {
@@ -181,57 +156,6 @@
)
}
- // TODO(b/318118018): Use from Jetpack
- private fun convertResponseToJson(response: GetCredentialResponse): String? {
- try {
- val jsonObject = JSONObject()
- jsonObject.put("type", "get")
- val jsonCred = JSONObject()
- jsonCred.put("type", response.credential.type)
- jsonCred.put("data", credentialToJSON(
- response.credential))
- jsonObject.put("credential", jsonCred)
- return jsonObject.toString()
- } catch (e: JSONException) {
- Log.i(
- TAG, "Exception while constructing response JSON: " +
- e.message
- )
- }
- return null
- }
-
- // TODO(b/318118018): Replace with calls to Jetpack
- private fun credentialToJSON(credential: Credential): JSONObject? {
- Log.i(TAG, "credentialToJSON")
- try {
- if (credential.type == "android.credentials.TYPE_PASSWORD_CREDENTIAL") {
- Log.i(TAG, "toJSON PasswordCredential")
-
- val json = JSONObject()
- val id = credential.data.getString("androidx.credentials.BUNDLE_KEY_ID")
- val pass = credential.data.getString("androidx.credentials.BUNDLE_KEY_PASSWORD")
- json.put("androidx.credentials.BUNDLE_KEY_ID", id)
- json.put("androidx.credentials.BUNDLE_KEY_PASSWORD", pass)
- return json
- } else if (credential.type == "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL") {
- Log.i(TAG, "toJSON PublicKeyCredential")
-
- val json = JSONObject()
- val responseJson = credential
- .data
- .getString("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON")
- json.put("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON",
- responseJson)
- return json
- }
- } catch (e: JSONException) {
- Log.i(TAG, "issue while converting credential response to JSON")
- }
- Log.i(TAG, "Unsupported credential type")
- return null
- }
-
private fun getEntryToIconMap(
candidateProviderDataList: List<GetCredentialProviderData>
): Map<String, Icon> {
@@ -275,6 +199,7 @@
val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
mapAutofillIdToProviders(candidateProviders)
val fillResponseBuilder = FillResponse.Builder()
+ fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE)
var validFillResponse = false
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
validFillResponse = processProvidersForAutofillId(
@@ -318,7 +243,7 @@
maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
- (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+ (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount - 1)
var i = 0
var datasetAdded = false
@@ -387,7 +312,7 @@
presentationBuilder.build())
.build())
.setAuthentication(pendingIntent.intentSender)
- .setAuthenticationExtras(fillInIntent.extras)
+ .setCredentialFillInIntent(fillInIntent)
.build())
datasetAdded = true
i++
@@ -407,11 +332,11 @@
}
private fun createInlinePresentation(
- primaryEntry: CredentialEntryInfo,
- pendingIntent: PendingIntent,
- icon: Icon,
- spec: InlinePresentationSpec,
- duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
+ primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
): InlinePresentation {
val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
primaryEntry.displayName != null) {
@@ -437,7 +362,8 @@
fillResponseBuilder: FillResponse.Builder
) {
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+ .setMenuPresentation(
+ RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
fillResponseBuilder.addDataset(
Dataset.Builder()
@@ -477,9 +403,8 @@
.setInlinePresentation(InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ true))
- val extraBundle = Bundle()
- extraBundle.putParcelableArrayList(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
+ val extrasIntent = Intent()
+ extrasIntent.putExtra(ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
fillResponseBuilder.addDataset(
dataSetBuilder
@@ -489,7 +414,7 @@
presentationBuilder.build())
.build())
.setAuthentication(bottomSheetPendingIntent.intentSender)
- .setAuthenticationExtras(extraBundle)
+ .setCredentialFillInIntent(extrasIntent)
.build()
)
}
@@ -640,7 +565,6 @@
autofillId: AutofillId,
responseClientState: Bundle
): List<CredentialOption> {
- // TODO(b/293945193) Replace with isCredential check from viewNode
val credentialHints: MutableList<String> = mutableListOf()
if (viewNode.autofillHints != null) {
for (hint in viewNode.autofillHints!!) {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index 3ed0c9c..b2812d3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -31,7 +31,7 @@
@Composable
fun AccountRow(
primaryText: String,
- secondaryText: String,
+ secondaryText: String? = null,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
@@ -42,14 +42,16 @@
maxLines = 1,
style = MaterialTheme.typography.title2
)
- Text(
- text = secondaryText,
- modifier = Modifier.padding(top = 7.dp),
- color = Color(0xFFCAC5BC),
- overflow = TextOverflow.Ellipsis,
- maxLines = 2,
- style = MaterialTheme.typography.body1,
- )
+ if (secondaryText != null) {
+ Text(
+ text = secondaryText,
+ modifier = Modifier.padding(top = 7.dp),
+ color = Color(0xFFCAC5BC),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
+ style = MaterialTheme.typography.body1,
+ )
+ }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 3297991..5590219 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -76,7 +76,7 @@
Chip(
label = labelParam,
onClick = onClick,
- modifier = modifier,
+ modifier = modifier.fillMaxWidth(),
secondaryLabel = secondaryLabelParam,
icon = iconParam,
colors = colors,
@@ -104,7 +104,6 @@
label = stringResource(R.string.dialog_sign_in_options_button),
onClick = onClick,
modifier = Modifier
- .fillMaxWidth()
.padding(top = TOPPADDING)
)
}
@@ -121,7 +120,6 @@
label = stringResource(R.string.dialog_continue_button),
onClick = onClick,
modifier = Modifier
- .fillMaxWidth()
.padding(top = TOPPADDING),
colors = ChipDefaults.primaryChipColors(),
)
@@ -139,7 +137,6 @@
label = stringResource(R.string.dialog_dismiss_button),
onClick = onClick,
modifier = Modifier
- .fillMaxWidth()
.padding(top = TOPPADDING),
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
deleted file mode 100644
index a368de2..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0N
- *
- * 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.credentialmanager.ui.model
-
-data class PasskeyUiModel(
- val name: String,
- val email: String,
-)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index 2878b0b..92d8a39 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 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.
@@ -18,44 +18,119 @@
package com.android.credentialmanager.ui.screens.single.passkey
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.Column
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
- name: String,
- email: String,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+ screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
+ viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
+ navController: NavHostController = rememberNavController(),
+) {
+ viewModel.initialize(credentialSelectorUiState.entry)
+
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ when (val state = uiState) {
+ UiState.CredentialScreen -> {
+ SinglePasskeyScreen(
+ credentialSelectorUiState.entry,
+ screenIcon,
+ columnState,
+ modifier,
+ viewModel
+ )
+ }
+
+ is UiState.CredentialSelected -> {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
+ }
+
+ SideEffect {
+ state.intentSenderRequest?.let {
+ launcher.launch(it)
+ }
+ }
+ }
+
+ UiState.Cancel -> {
+ // TODO(b/322797032) add valid navigation path here for going back
+ navController.popBackStack()
+ }
+ }
+}
+
+@OptIn(ExperimentalHorologistApi::class)
+@Composable
+fun SinglePasskeyScreen(
+ entry: CredentialEntryInfo,
+ screenIcon: Drawable?,
+ columnState: ScalingLazyColumnState,
+ modifier: Modifier = Modifier,
+ viewModel: SinglePasskeyScreenViewModel,
) {
SingleAccountScreen(
headerContent = {
SignInHeader(
- icon = null,
+ icon = screenIcon,
title = stringResource(R.string.use_passkey_title),
)
},
accountContent = {
- AccountRow(
- primaryText = name,
- secondaryText = email,
- modifier = Modifier.padding(top = 10.dp),
- )
+ if (entry.displayName != null) {
+ AccountRow(
+ primaryText = checkNotNull(entry.displayName),
+ secondaryText = entry.userName,
+ modifier = Modifier.padding(top = 10.dp),
+ )
+ } else {
+ AccountRow(
+ primaryText = entry.userName,
+ modifier = Modifier.padding(top = 10.dp),
+ )
+ }
},
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
+ Column {
+ ContinueChip(viewModel::onContinueClick)
+ SignInOptionsChip(viewModel::onSignInOptionsClick)
+ DismissChip(viewModel::onDismissClick)
+ }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
new file mode 100644
index 0000000..35c39f6
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.credentialmanager.ui.screens.single.passkey
+
+import android.content.Intent
+import android.credentials.selection.UserSelectionDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.ktx.getIntentSenderRequest
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import dagger.hilt.android.lifecycle.HiltViewModel
+import com.android.credentialmanager.ui.screens.single.UiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+@HiltViewModel
+class SinglePasskeyScreenViewModel @Inject constructor(
+ private val credentialManagerClient: CredentialManagerClient,
+) : ViewModel() {
+
+ private val _uiState =
+ MutableStateFlow<UiState>(UiState.CredentialScreen)
+ val uiState: StateFlow<UiState> = _uiState
+
+ private lateinit var requestGet: Request.Get
+ private lateinit var entryInfo: CredentialEntryInfo
+
+
+ @MainThread
+ fun initialize(entry: CredentialEntryInfo) {
+ this.entryInfo = entry
+ }
+
+ fun onDismissClick() {
+ _uiState.value = UiState.Cancel
+ }
+
+ fun onContinueClick() {
+ _uiState.value = UiState.CredentialSelected(
+ intentSenderRequest = entryInfo.getIntentSenderRequest()
+ )
+ }
+
+ fun onSignInOptionsClick() {
+ }
+
+ fun onPasskeyInfoRetrieved(
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ ) {
+ val userSelectionDialogResult = UserSelectionDialogResult(
+ requestGet.token,
+ entryInfo.providerId,
+ entryInfo.entryKey,
+ entryInfo.entrySubkey,
+ if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+ )
+ credentialManagerClient.sendResult(userSelectionDialogResult)
+ }
+}
+
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f425f52..3242935 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -288,7 +288,7 @@
cannot happen immediately because the device is offline (has no internet connection.
[CHAR LIMIT=none] -->
<string name="unarchive_error_offline_body">
- This app will automatically restore when you\'re connected to the internet
+ To restore this app, check your internet connection and try again
</string>
<!-- Dialog title shown when the user is trying to restore an app but a generic error happened.
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index b2b7b61..5f5f1d5 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -6,7 +6,6 @@
edgarwang@google.com
evanlaird@google.com
juliacr@google.com
-yantingyang@google.com
ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index f44b161..aed985e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -14,10 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearance.PreferenceTitle.SettingsLib"
parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:textColor">@color/settingslib_text_color_primary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20sp</item>
</style>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 4b5a9bc..b55dd1b 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -24,4 +24,8 @@
</style>
<style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
+ <style name="Theme.SpaLib.BottomSheetDialog" parent="Theme.SpaLib">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index b34c310..e704505 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -18,7 +18,6 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
@@ -95,56 +94,60 @@
if (options.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded,
- modifier = Modifier
- .fillMaxWidth()
- .width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+ modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
onDismissRequest = { expanded = false },
) {
options.forEachIndexed { index, option ->
- TextButton(
- modifier = Modifier
- .fillMaxHeight()
- .fillMaxWidth(),
- onClick = {
- if (selectedOptionsState.contains(index)) {
- if (index == allIndex)
- selectedOptionsState.clear()
- else {
- selectedOptionsState.remove(
- index
- )
- if (selectedOptionsState.contains(allIndex))
- selectedOptionsState.remove(
- allIndex
- )
- }
- } else {
- selectedOptionsState.add(
- index
- )
- }
- onSelectedOptionStateChange()
- }) {
- Row(
- modifier = Modifier
- .fillMaxHeight()
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.Start,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Checkbox(
- checked = selectedOptionsState.contains(index),
- onCheckedChange = null,
- )
- Text(text = option)
- }
- }
+ CheckboxItem(
+ selectedOptionsState,
+ index,
+ allIndex,
+ onSelectedOptionStateChange,
+ option,
+ )
}
}
}
}
}
+@Composable
+private fun CheckboxItem(
+ selectedOptionsState: SnapshotStateList<Int>,
+ index: Int,
+ allIndex: Int,
+ onSelectedOptionStateChange: () -> Unit,
+ option: String
+) {
+ TextButton(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {
+ if (selectedOptionsState.contains(index)) {
+ if (index == allIndex) {
+ selectedOptionsState.clear()
+ } else {
+ selectedOptionsState.remove(index)
+ selectedOptionsState.remove(allIndex)
+ }
+ } else {
+ selectedOptionsState.add(index)
+ }
+ onSelectedOptionStateChange()
+ }) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ checked = selectedOptionsState.contains(index),
+ onCheckedChange = null,
+ )
+ Text(text = option)
+ }
+ }
+}
+
@Preview
@Composable
private fun ActionButtonsPreview() {
@@ -158,4 +161,4 @@
enabled = true,
onSelectedOptionStateChange = {})
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 0a98791..988afd7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,10 +17,11 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
-import android.content.pm.FeatureFlags
-import android.content.pm.FeatureFlagsImpl
import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
+import android.content.pm.Flags
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
@@ -85,13 +86,7 @@
loadInstantApps: Boolean,
matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> = coroutineScope {
- val hiddenSystemModulesDeferred = async {
- packageManager.getInstalledModules(0)
- .filter { it.isHidden }
- .map { it.packageName }
- .filterNotNull()
- .toSet()
- }
+ val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
val hideWhenDisabledPackagesDeferred = async {
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
}
@@ -205,6 +200,15 @@
private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
+ private fun PackageManager.getHiddenSystemModules(): Set<String> {
+ val moduleInfos = getInstalledModules(0).filter { it.isHidden }
+ val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
+ if (Flags.provideInfoOfApkInApex()) {
+ hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames }
+ }
+ return hiddenApps
+ }
+
companion object {
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index a28ebc6..458fcc9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -35,5 +35,6 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
+ "flag-junit",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index f292231..efd53a4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -21,6 +21,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.FakeFeatureFlagsImpl
import android.content.pm.Flags
+import android.content.pm.ModuleInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -28,6 +29,7 @@
import android.content.pm.UserInfo
import android.content.res.Resources
import android.os.UserManager
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
@@ -35,6 +37,7 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -50,6 +53,9 @@
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+
private val resources = mock<Resources> {
on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
}
@@ -273,6 +279,38 @@
}
@Test
+ fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+ packageManager.stub {
+ on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+ }
+ mockInstalledApplications(
+ listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+ ADMIN_USER_ID
+ )
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ }
+
+ @Test
+ fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
+ mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+ packageManager.stub {
+ on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+ }
+ mockInstalledApplications(
+ listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+ ADMIN_USER_ID
+ )
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
+ }
+
+ @Test
fun showSystemPredicate_showSystem() = runTest {
val app = SYSTEM_APP
@@ -402,6 +440,20 @@
isArchived = true
}
+ val HIDDEN_APEX_APP = ApplicationInfo().apply {
+ packageName = "hidden.apex.package"
+ }
+
+ val HIDDEN_MODULE_APP = ApplicationInfo().apply {
+ packageName = "hidden.module.package"
+ }
+
+ val HIDDEN_MODULE = ModuleInfo().apply {
+ packageName = "hidden.module.package"
+ apkInApexPackageNames = listOf("hidden.apex.package")
+ isHidden = true
+ }
+
fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
activityInfo = ActivityInfo().apply {
this.packageName = packageName
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 07de7fd..c482995 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -164,12 +165,16 @@
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
- // Check if the package is contained in an APEX. There is no public API to properly
- // check whether a given APK package comes from an APEX registered as module.
- // Therefore we conservatively assume that any package scanned from an /apex path is
- // a system package.
- return pkg.applicationInfo.sourceDir.startsWith(
- Environment.getApexDirectory().getAbsolutePath());
+ if (Flags.provideInfoOfApkInApex()) {
+ return pkg.getApexPackageName() != null;
+ } else {
+ // Check if the package is contained in an APEX. There is no public API to properly
+ // check whether a given APK package comes from an APEX registered as module.
+ // Therefore we conservatively assume that any package scanned from an /apex path is
+ // a system package.
+ return pkg.applicationInfo.sourceDir.startsWith(
+ Environment.getApexDirectory().getAbsolutePath());
+ }
} catch (PackageManager.NameNotFoundException e) {
return false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 8e1067f..e3012cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.ModuleInfo;
@@ -226,6 +227,11 @@
final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
for (ModuleInfo info : moduleInfos) {
mSystemModules.put(info.getPackageName(), info.isHidden());
+ if (Flags.provideInfoOfApkInApex()) {
+ for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+ mSystemModules.put(apkInApexPackageName, info.isHidden());
+ }
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
new file mode 100644
index 0000000..2a4658b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.media.data.repository
+
+import android.media.AudioDeviceAttributes
+import android.media.Spatializer
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+interface SpatializerRepository {
+
+ /**
+ * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
+ * false the otherwise.
+ */
+ suspend fun isAvailableForDevice(audioDeviceAttributes: AudioDeviceAttributes): Boolean
+
+ /** Returns a list [AudioDeviceAttributes] that are compatible with spatial audio. */
+ suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes>
+
+ /** Adds a [audioDeviceAttributes] to [getCompatibleDevices] list. */
+ suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+
+ /** Removes a [audioDeviceAttributes] to [getCompatibleDevices] list. */
+ suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+}
+
+class SpatializerRepositoryImpl(
+ private val spatializer: Spatializer,
+ private val backgroundContext: CoroutineContext,
+) : SpatializerRepository {
+
+ override suspend fun isAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean {
+ return withContext(backgroundContext) {
+ spatializer.isAvailableForDevice(audioDeviceAttributes)
+ }
+ }
+
+ override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+ withContext(backgroundContext) { spatializer.compatibleAudioDevices }
+
+ override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ withContext(backgroundContext) {
+ spatializer.addCompatibleAudioDevice(audioDeviceAttributes)
+ }
+ }
+
+ override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ withContext(backgroundContext) {
+ spatializer.removeCompatibleAudioDevice(audioDeviceAttributes)
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
new file mode 100644
index 0000000..c3cc340
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+
+class SpatializerInteractor(private val repository: SpatializerRepository) {
+
+ suspend fun isAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.isAvailableForDevice(audioDeviceAttributes)
+
+ /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
+ suspend fun isEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.getCompatibleDevices().contains(audioDeviceAttributes)
+
+ /** Enblaes or disables spatial audio for [audioDeviceAttributes]. */
+ suspend fun setEnabled(audioDeviceAttributes: AudioDeviceAttributes, isEnabled: Boolean) {
+ if (isEnabled) {
+ repository.addCompatibleDevice(audioDeviceAttributes)
+ } else {
+ repository.removeCompatibleDevice(audioDeviceAttributes)
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt
new file mode 100644
index 0000000..a98f3e2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.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.settingslib.volume.data.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+ val routingSessionInfo: RoutingSessionInfo,
+ val isVolumeSeekBarEnabled: Boolean,
+ val isMediaOutputDisabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 6761aa7..f729c04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,15 +16,12 @@
package com.android.settingslib.volume.data.repository
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.OnCommunicationDeviceChangedListener
import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
@@ -32,7 +29,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
@@ -40,7 +36,6 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -77,7 +72,7 @@
}
class AudioRepositoryImpl(
- private val context: Context,
+ private val audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
private val audioManager: AudioManager,
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
@@ -93,30 +88,9 @@
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
- private val audioManagerIntents: SharedFlow<String> =
- callbackFlow {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent) {
- intent.action?.let { action -> launch { send(action) } }
- }
- }
- context.registerReceiver(
- receiver,
- IntentFilter().apply {
- for (action in allActions) {
- addAction(action)
- }
- }
- )
-
- awaitClose { context.unregisterReceiver(receiver) }
- }
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
-
override val ringerMode: StateFlow<RingerMode> =
- audioManagerIntents
- .filter { ringerActions.contains(it) }
+ audioManagerIntentsReceiver.intents
+ .filter { AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION == it.action }
.map { RingerMode(audioManager.ringerModeInternal) }
.flowOn(backgroundCoroutineContext)
.stateIn(
@@ -146,8 +120,7 @@
)
override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
- return audioManagerIntents
- .filter { modelActions.contains(it) }
+ return audioManagerIntentsReceiver.intents
.map { getCurrentAudioStream(audioStream) }
.flowOn(backgroundCoroutineContext)
}
@@ -189,20 +162,4 @@
// return STREAM_VOICE_CALL in getAudioStream
audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
}
-
- private companion object {
- val modelActions =
- setOf(
- AudioManager.STREAM_MUTE_CHANGED_ACTION,
- AudioManager.MASTER_MUTE_CHANGED_ACTION,
- AudioManager.VOLUME_CHANGED_ACTION,
- AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
- AudioManager.STREAM_DEVICES_CHANGED_ACTION,
- )
- val ringerActions =
- setOf(
- AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
- )
- val allActions = ringerActions + modelActions
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 1597b77..aa9ae76 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -15,8 +15,13 @@
*/
package com.android.settingslib.volume.data.repository
+import android.media.AudioManager
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -24,10 +29,15 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Repository providing data about connected media devices. */
interface LocalMediaRepository {
@@ -37,43 +47,55 @@
/** Currently connected media device */
val currentConnectedDevice: StateFlow<MediaDevice?>
+
+ val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+
+ suspend fun adjustSessionVolume(sessionId: String?, volume: Int)
}
class LocalMediaRepositoryImpl(
+ audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
private val localMediaManager: LocalMediaManager,
+ private val mediaRouter2Manager: MediaRouter2Manager,
coroutineScope: CoroutineScope,
- backgroundContext: CoroutineContext,
+ private val backgroundContext: CoroutineContext,
) : LocalMediaRepository {
- private val deviceUpdates: Flow<DevicesUpdate> = callbackFlow {
- val callback =
- object : LocalMediaManager.DeviceCallback {
- override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
- trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
- }
+ private val devicesChanges =
+ audioManagerIntentsReceiver.intents.filter {
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+ }
+ private val mediaDevicesUpdates: Flow<DevicesUpdate> =
+ callbackFlow {
+ val callback =
+ object : LocalMediaManager.DeviceCallback {
+ override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
+ trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
+ }
- override fun onSelectedDeviceStateChanged(
- device: MediaDevice?,
- state: Int,
- ) {
- trySend(DevicesUpdate.SelectedDeviceStateChanged)
- }
+ override fun onSelectedDeviceStateChanged(
+ device: MediaDevice?,
+ state: Int,
+ ) {
+ trySend(DevicesUpdate.SelectedDeviceStateChanged)
+ }
- override fun onDeviceAttributesChanged() {
- trySend(DevicesUpdate.DeviceAttributesChanged)
+ override fun onDeviceAttributesChanged() {
+ trySend(DevicesUpdate.DeviceAttributesChanged)
+ }
+ }
+ localMediaManager.registerCallback(callback)
+ localMediaManager.startScan()
+
+ awaitClose {
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(callback)
}
}
- localMediaManager.registerCallback(callback)
- localMediaManager.startScan()
-
- awaitClose {
- localMediaManager.stopScan()
- localMediaManager.unregisterCallback(callback)
- }
- }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
override val mediaDevices: StateFlow<Collection<MediaDevice>> =
- deviceUpdates
+ mediaDevicesUpdates
.mapNotNull {
if (it is DevicesUpdate.DeviceListUpdate) {
it.newDevices ?: emptyList()
@@ -85,7 +107,7 @@
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
override val currentConnectedDevice: StateFlow<MediaDevice?> =
- deviceUpdates
+ merge(devicesChanges, mediaDevicesUpdates)
.map { localMediaManager.currentConnectedDevice }
.stateIn(
coroutineScope,
@@ -93,6 +115,30 @@
localMediaManager.currentConnectedDevice
)
+ override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>> =
+ merge(devicesChanges, mediaDevicesUpdates)
+ .onStart { emit(Unit) }
+ .map { localMediaManager.remoteRoutingSessions.map(::toRoutingSession) }
+ .flowOn(backgroundContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+ withContext(backgroundContext) {
+ if (sessionId == null) {
+ localMediaManager.adjustSessionVolume(volume)
+ } else {
+ localMediaManager.adjustSessionVolume(sessionId, volume)
+ }
+ }
+ }
+
+ private fun toRoutingSession(info: RoutingSessionInfo): RoutingSession =
+ RoutingSession(
+ info,
+ isMediaOutputDisabled = mediaRouter2Manager.getTransferableRoutes(info).isEmpty(),
+ isVolumeSeekBarEnabled = localMediaManager.shouldEnableVolumeSeekBar(info)
+ )
+
private sealed interface DevicesUpdate {
data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index 93aa90d..ab8c6b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -16,30 +16,23 @@
package com.android.settingslib.volume.data.repository
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
import android.media.AudioManager
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** Provides controllers for currently active device media sessions. */
interface MediaControllerRepository {
@@ -49,40 +42,25 @@
}
class MediaControllerRepositoryImpl(
- private val context: Context,
+ audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
private val mediaSessionManager: MediaSessionManager,
localBluetoothManager: LocalBluetoothManager?,
coroutineScope: CoroutineScope,
backgroundContext: CoroutineContext,
) : MediaControllerRepository {
- private val devicesChanges: Flow<Unit> =
- callbackFlow {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- if (AudioManager.STREAM_DEVICES_CHANGED_ACTION == intent?.action) {
- launch { send(Unit) }
- }
- }
- }
- context.registerReceiver(
- receiver,
- IntentFilter(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
- )
-
- awaitClose { context.unregisterReceiver(receiver) }
- }
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
-
+ private val devicesChanges =
+ audioManagerIntentsReceiver.intents.filter {
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+ }
override val activeMediaController: StateFlow<MediaController?> =
- combine(
- localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
- ?: emptyFlow(),
- devicesChanges.onStart { emit(Unit) },
- ) { _, _ ->
- getActiveLocalMediaController()
+ buildList {
+ localBluetoothManager?.headsetAudioModeChanges?.let { add(it) }
+ add(devicesChanges)
}
+ .merge()
+ .onStart { emit(Unit) }
+ .map { getActiveLocalMediaController() }
.flowOn(backgroundContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
new file mode 100644
index 0000000..f621335
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.volume.domain.interactor
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.domain.model.RoutingSession
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class LocalMediaInteractor(
+ private val repository: LocalMediaRepository,
+ coroutineScope: CoroutineScope,
+) {
+
+ /** Available devices list */
+ val mediaDevices: StateFlow<Collection<MediaDevice>>
+ get() = repository.mediaDevices
+
+ /** Currently connected media device */
+ val currentConnectedDevice: StateFlow<MediaDevice?>
+ get() = repository.currentConnectedDevice
+
+ val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
+ repository.remoteRoutingSessions
+ .map { sessions ->
+ sessions.map {
+ RoutingSession(
+ routingSessionInfo = it.routingSessionInfo,
+ isMediaOutputDisabled = it.isMediaOutputDisabled,
+ isVolumeSeekBarEnabled =
+ it.isVolumeSeekBarEnabled && it.routingSessionInfo.volumeMax > 0
+ )
+ }
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+ suspend fun adjustSessionVolume(sessionId: String?, volume: Int) =
+ repository.adjustSessionVolume(sessionId, volume)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt
new file mode 100644
index 0000000..dfc4703
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.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.settingslib.volume.domain.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+ val routingSessionInfo: RoutingSessionInfo,
+ val isMediaOutputDisabled: Boolean,
+ val isVolumeSeekBarEnabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..9fa4c86
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.volume.shared
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+/** Exposes [AudioManager] intents as a observable shared flow. */
+interface AudioManagerIntentsReceiver {
+
+ val intents: SharedFlow<Intent>
+}
+
+class AudioManagerIntentsReceiverImpl(
+ private val context: Context,
+ coroutineScope: CoroutineScope,
+) : AudioManagerIntentsReceiver {
+
+ private val allActions: Collection<String>
+ get() =
+ setOf(
+ AudioManager.STREAM_MUTE_CHANGED_ACTION,
+ AudioManager.MASTER_MUTE_CHANGED_ACTION,
+ AudioManager.VOLUME_CHANGED_ACTION,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ )
+
+ override val intents: SharedFlow<Intent> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ launch { send(intent) }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ for (action in allActions) {
+ addAction(action)
+ }
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .filterNotNull()
+ .filter { intent -> allActions.contains(intent.action) }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
new file mode 100644
index 0000000..3f52f24
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+
+class FakeSpatializerRepository : SpatializerRepository {
+
+ private val availabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf()
+ private val compatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
+
+ override suspend fun isAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean = availabilityByDevice.getOrDefault(audioDeviceAttributes, false)
+
+ override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+ compatibleDevices
+
+ override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ compatibleDevices.add(audioDeviceAttributes)
+ }
+
+ override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ compatibleDevices.remove(audioDeviceAttributes)
+ }
+
+ fun setIsAvailable(audioDeviceAttributes: AudioDeviceAttributes, isAvailable: Boolean) {
+ availabilityByDevice[audioDeviceAttributes] = isAvailable
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
new file mode 100644
index 0000000..a44baeb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SpatializerInteractorTest {
+
+ private val testScope = TestScope()
+ private val underTest = SpatializerInteractor(FakeSpatializerRepository())
+
+ @Test
+ fun setEnabledFalse_isEnabled_false() {
+ testScope.runTest {
+ underTest.setEnabled(deviceAttributes, false)
+
+ assertThat(underTest.isEnabled(deviceAttributes)).isFalse()
+ }
+ }
+
+ @Test
+ fun setEnabledTrue_isEnabled_true() {
+ testScope.runTest {
+ underTest.setEnabled(deviceAttributes, true)
+
+ assertThat(underTest.isEnabled(deviceAttributes)).isTrue()
+ }
+ }
+
+ private companion object {
+ val deviceAttributes = AudioDeviceAttributes(0, 0, "test_device")
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 7b70c64..48b04db 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -16,13 +16,11 @@
package com.android.settingslib.volume.data.repository
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,6 +28,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -51,17 +50,16 @@
@RunWith(AndroidJUnit4::class)
class AudioRepositoryTest {
- @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
@Captor
private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
@Captor
private lateinit var communicationDeviceListenerCaptor:
ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
- @Mock private lateinit var context: Context
@Mock private lateinit var audioManager: AudioManager
@Mock private lateinit var communicationDevice: AudioDeviceInfo
+ private val intentsReceiver = FakeAudioManagerIntentsReceiver()
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -98,7 +96,7 @@
underTest =
AudioRepositoryImpl(
- context,
+ intentsReceiver,
audioManager,
testScope.testScheduler,
testScope.backgroundScope,
@@ -270,8 +268,7 @@
}
private fun triggerIntent(action: String) {
- verify(context).registerReceiver(receiverCaptor.capture(), any())
- receiverCaptor.value.onReceive(context, Intent(action))
+ testScope.launch { intentsReceiver.triggerIntent(action) }
}
private companion object {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
new file mode 100644
index 0000000..642b72c
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.volume.data.repository
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeLocalMediaRepository : LocalMediaRepository {
+
+ private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
+
+ private val mutableMediaDevices = MutableStateFlow<Collection<MediaDevice>>(emptyList())
+ override val mediaDevices: StateFlow<Collection<MediaDevice>>
+ get() = mutableMediaDevices.asStateFlow()
+
+ private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
+ override val currentConnectedDevice: StateFlow<MediaDevice?>
+ get() = mutableCurrentConnectedDevice.asStateFlow()
+
+ private val mutableRemoteRoutingSessions =
+ MutableStateFlow<Collection<RoutingSession>>(emptyList())
+ override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+ get() = mutableRemoteRoutingSessions.asStateFlow()
+
+ fun updateMediaDevices(devices: Collection<MediaDevice>) {
+ mutableMediaDevices.value = devices
+ }
+
+ fun updateCurrentConnectedDevice(device: MediaDevice?) {
+ mutableCurrentConnectedDevice.value = device
+ }
+
+ fun updateRemoteRoutingSessions(sessions: List<RoutingSession>) {
+ mutableRemoteRoutingSessions.value = sessions
+ }
+
+ fun getSessionVolume(sessionId: String?): Int = volumeBySession.getOrDefault(sessionId, 0)
+
+ override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+ volumeBySession[sessionId] = volume
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
index d106bce..dc9ea10 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -15,10 +15,15 @@
*/
package com.android.settingslib.volume.data.repository
+import android.media.MediaRoute2Info
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -32,6 +37,10 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -44,10 +53,12 @@
@Mock private lateinit var localMediaManager: LocalMediaManager
@Mock private lateinit var mediaDevice1: MediaDevice
@Mock private lateinit var mediaDevice2: MediaDevice
+ @Mock private lateinit var mediaRouter2Manager: MediaRouter2Manager
@Captor
private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
+ private val intentsReceiver = FakeAudioManagerIntentsReceiver()
private val testScope = TestScope()
private lateinit var underTest: LocalMediaRepository
@@ -58,7 +69,9 @@
underTest =
LocalMediaRepositoryImpl(
+ intentsReceiver,
localMediaManager,
+ mediaRouter2Manager,
testScope.backgroundScope,
testScope.testScheduler,
)
@@ -97,4 +110,78 @@
assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
}
}
+
+ @Test
+ fun kek() {
+ testScope.runTest {
+ `when`(localMediaManager.remoteRoutingSessions)
+ .thenReturn(
+ listOf(
+ testRoutingSessionInfo1,
+ testRoutingSessionInfo2,
+ testRoutingSessionInfo3,
+ )
+ )
+ `when`(localMediaManager.shouldEnableVolumeSeekBar(any())).then {
+ (it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo1
+ }
+ `when`(mediaRouter2Manager.getTransferableRoutes(any<RoutingSessionInfo>())).then {
+ if ((it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo2) {
+ return@then listOf(mock(MediaRoute2Info::class.java))
+ }
+ emptyList<MediaRoute2Info>()
+ }
+ var remoteRoutingSessions: Collection<RoutingSession>? = null
+ underTest.remoteRoutingSessions
+ .onEach { remoteRoutingSessions = it }
+ .launchIn(backgroundScope)
+
+ runCurrent()
+
+ assertThat(remoteRoutingSessions)
+ .containsExactlyElementsIn(
+ listOf(
+ RoutingSession(
+ routingSessionInfo = testRoutingSessionInfo1,
+ isVolumeSeekBarEnabled = true,
+ isMediaOutputDisabled = true,
+ ),
+ RoutingSession(
+ routingSessionInfo = testRoutingSessionInfo2,
+ isVolumeSeekBarEnabled = false,
+ isMediaOutputDisabled = false,
+ ),
+ RoutingSession(
+ routingSessionInfo = testRoutingSessionInfo3,
+ isVolumeSeekBarEnabled = false,
+ isMediaOutputDisabled = true,
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustSessionVolume_adjusts() {
+ testScope.runTest {
+ var volume = 0
+ `when`(localMediaManager.adjustSessionVolume(anyString(), anyInt())).then {
+ volume = it.arguments[1] as Int
+ Unit
+ }
+
+ underTest.adjustSessionVolume("test_session", 10)
+
+ assertThat(volume).isEqualTo(10)
+ }
+ }
+
+ private companion object {
+ val testRoutingSessionInfo1 =
+ RoutingSessionInfo.Builder("id_1", "test.pkg.1").addSelectedRoute("route_1").build()
+ val testRoutingSessionInfo2 =
+ RoutingSessionInfo.Builder("id_2", "test.pkg.2").addSelectedRoute("route_2").build()
+ val testRoutingSessionInfo3 =
+ RoutingSessionInfo.Builder("id_3", "test.pkg.3").addSelectedRoute("route_3").build()
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index f07b1bff..430d733 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -16,9 +16,6 @@
package com.android.settingslib.volume.data.repository
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
import android.media.AudioManager
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
@@ -29,6 +26,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -52,10 +50,8 @@
@SmallTest
class MediaControllerRepositoryImplTest {
- @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
@Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
- @Mock private lateinit var context: Context
@Mock private lateinit var mediaSessionManager: MediaSessionManager
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
@Mock private lateinit var eventManager: BluetoothEventManager
@@ -70,6 +66,7 @@
@Mock private lateinit var localPlaybackInfo: PlaybackInfo
private val testScope = TestScope()
+ private val intentsReceiver = FakeAudioManagerIntentsReceiver()
private lateinit var underTest: MediaControllerRepository
@@ -97,7 +94,7 @@
underTest =
MediaControllerRepositoryImpl(
- context,
+ intentsReceiver,
mediaSessionManager,
localBluetoothManager,
testScope.backgroundScope,
@@ -124,7 +121,7 @@
.launchIn(backgroundScope)
runCurrent()
- triggerDevicesChange()
+ intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
triggerOnAudioModeChanged()
runCurrent()
@@ -149,7 +146,7 @@
.launchIn(backgroundScope)
runCurrent()
- triggerDevicesChange()
+ intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
triggerOnAudioModeChanged()
runCurrent()
@@ -157,22 +154,19 @@
}
}
- private fun triggerDevicesChange() {
- verify(context).registerReceiver(receiverCaptor.capture(), any())
- receiverCaptor.value.onReceive(context, Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
- }
-
private fun triggerOnAudioModeChanged() {
verify(eventManager).registerCallback(callbackCaptor.capture())
callbackCaptor.value.onAudioModeChanged()
}
private companion object {
- val statePlaying =
+ val statePlaying: PlaybackState =
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
- val stateError = PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
- val stateStopped =
+ val stateError: PlaybackState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
+ val stateStopped: PlaybackState =
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
- val stateNone = PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
+ val stateNone: PlaybackState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..530690a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.volume.shared
+
+import android.content.Intent
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAudioManagerIntentsReceiver : AudioManagerIntentsReceiver {
+
+ private val mutableIntents = MutableSharedFlow<Intent>()
+ override val intents: SharedFlow<Intent> = mutableIntents.asSharedFlow()
+
+ suspend fun triggerIntent(intent: Intent) {
+ mutableIntents.emit(intent)
+ }
+
+ suspend fun triggerIntent(action: String) {
+ triggerIntent(Intent(action))
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 994c1ee..e32d880 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.applications;
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -24,12 +26,16 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.Utils;
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;
@@ -39,6 +45,8 @@
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
import java.io.File;
import java.lang.reflect.Field;
@@ -60,6 +68,10 @@
private ApplicationInfo mAppInfo;
private ApplicationsState.AppEntry mAppEntry;
private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+ private ShadowPackageManager mShadowPackageManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -70,6 +82,7 @@
mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
doReturn(mIcon).when(mIcon).mutate();
+ mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
}
@After
@@ -151,6 +164,32 @@
assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
}
+ @Test
+ public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
+ mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = APP_PACKAGE_NAME;
+ packageInfo.setApexPackageName("com.test.apex.package");
+ mShadowPackageManager.installPackage(packageInfo);
+
+ assertThat(
+ AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+ }
+
+ @Test
+ public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
+ mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = APP_PACKAGE_NAME;
+ packageInfo.applicationInfo = applicationInfo;
+ mShadowPackageManager.installPackage(packageInfo);
+
+ assertThat(
+ AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+ }
+
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 34d8148..827d8fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
import static android.os.UserHandle.MU_ENABLED;
import static android.os.UserHandle.USER_SYSTEM;
@@ -58,6 +59,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
@@ -70,6 +72,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;
@@ -89,6 +92,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@@ -137,6 +141,9 @@
@Mock
private IPackageManager mPackageManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Implements(value = IconDrawableFactory.class)
public static class ShadowIconDrawableFactory {
@@ -169,7 +176,9 @@
public List<ModuleInfo> getInstalledModules(int flags) {
if (mInstalledModules.isEmpty()) {
for (String moduleName : mModuleNames) {
- mInstalledModules.add(createModuleInfo(moduleName));
+ mInstalledModules.add(
+ createModuleInfo(moduleName,
+ TextUtils.concat(moduleName, ".apex").toString()));
}
}
return mInstalledModules;
@@ -188,10 +197,11 @@
return resolveInfos;
}
- private ModuleInfo createModuleInfo(String packageName) {
+ private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
final ModuleInfo info = new ModuleInfo();
info.setName(packageName);
info.setPackageName(packageName);
+ info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
// will treat any app with package name that contains "hidden" as hidden module
info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
return info;
@@ -822,4 +832,32 @@
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
.isEqualTo(PKG_1);
}
+
+ @Test
+ public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
+ mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationsState.sInstance = null;
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+ String normalModulePackage = "test.module.1";
+ String hiddenModulePackage = "test.hidden.module.2";
+ String hiddenApexPackage = "test.hidden.module.2.apex";
+
+ assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+ assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+ assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
+ }
+
+ @Test
+ public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
+ mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationsState.sInstance = null;
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+ String normalModulePackage = "test.module.1";
+ String hiddenModulePackage = "test.hidden.module.2";
+ String hiddenApexPackage = "test.hidden.module.2.apex";
+
+ assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+ assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+ assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ad5f24..dc8116d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -261,6 +261,7 @@
Settings.Secure.CREDENTIAL_SERVICE,
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
Settings.Secure.EVEN_DIMMER_ACTIVATED,
- Settings.Secure.EVEN_DIMMER_MIN_NITS
+ Settings.Secure.EVEN_DIMMER_MIN_NITS,
+ Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d854df38..fabdafc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -416,5 +416,6 @@
VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
+ VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e4a762a..bc07836 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2601,6 +2601,9 @@
p.end(soundsToken);
dumpSetting(s, p,
+ Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+ SecureSettingsProto.STYLUS_POINTER_ICON_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
SecureSettingsProto.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e99fcc9..84ef6e5 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -914,6 +914,9 @@
<!-- Permission required for Cts test ScreenRecordingCallbackTests -->
<uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+ <!-- Permissions required for CTS test - GrammaticalInflectionManagerTest -->
+ <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SoundPicker/OWNERS b/packages/SoundPicker/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
deleted file mode 100644
index f4d8bf2..0000000
--- a/packages/SoundPicker2/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_library {
- name: "SoundPicker2Lib",
- srcs: [
- "src/**/*.java",
- ],
- resource_dirs: [
- "res",
- ],
- static_libs: [
- "androidx.appcompat_appcompat",
- "hilt_android",
- "guava",
- "androidx.recyclerview_recyclerview",
- "androidx-constraintlayout_constraintlayout",
- "androidx.viewpager2_viewpager2",
- "com.google.android.material_material",
- ],
-}
-
-android_app {
- name: "SoundPicker2",
- defaults: ["platform_app_defaults"],
- manifest: "AndroidManifest.xml",
- static_libs: ["SoundPicker2Lib"],
- platform_apis: true,
- certificate: "media",
- privileged: true,
-
- optimize: {
- enabled: true,
- optimize: true,
- shrink: true,
- shrink_resources: true,
- obfuscate: false,
- proguard_compatibility: false,
- },
-}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
deleted file mode 100644
index 934b003..0000000
--- a/packages/SoundPicker2/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.soundpicker"
- android:sharedUserId="android.media">
-
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
- <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
- <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
- <application
- android:name=".RingtonePickerApplication"
- android:allowBackup="false"
- android:label="@string/app_label"
- android:theme="@style/Theme.AppCompat"
- android:supportsRtl="true">
- <receiver android:name="RingtoneReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
- </intent-filter>
- </receiver>
-
- <service android:name="RingtoneOverlayService" />
-
- <activity android:name="RingtonePickerActivity"
- android:theme="@style/Theme.AppCompat.Dialog"
- android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
- android:excludeFromRecents="true"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.RINGTONE_PICKER" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
- <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
- <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
deleted file mode 100644
index 22b3fe9..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:fillColor="?android:attr/colorAccent"
- android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
deleted file mode 100644
index c376867..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add_padded.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- Copyright (C) 2017 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.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_add"
- android:insetTop="4dp"
- android:insetRight="4dp"
- android:insetBottom="4dp"
- android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
deleted file mode 100644
index edfc0ab..0000000
--- a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 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.
--->
-
-<!--
- Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
- Make the visibility to "gone" to prevent failures.
- -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/add_new_sound_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@null"
- android:textColor="?android:attr/colorAccent"
- android:gravity="center_vertical"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:drawableStart="@drawable/ic_add_padded"
- android:drawablePadding="8dp"
- android:ellipsize="marquee"
- android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
deleted file mode 100644
index ee29a37..0000000
--- a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- >
-
- <CheckedTextView
- android:id="@+id/checked_text_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorAlertDialogListItem"
- android:gravity="center_vertical"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:drawableStart="?android:attr/listChoiceIndicatorSingle"
- android:drawablePadding="8dp"
- android:ellipsize="marquee"
- android:layout_toLeftOf="@+id/work_icon"
- android:maxLines="3" />
-
- <ImageView
- android:id="@id/work_icon"
- android:layout_width="18dp"
- android:layout_height="18dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:scaleType="centerCrop"
- android:layout_marginRight="20dp" />
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
deleted file mode 100644
index 6fc6080..0000000
--- a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
deleted file mode 100644
index 024b97e..0000000
--- a/packages/SoundPicker2/res/layout/add_new_sound_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
- android:clickable="true">
-
- <ImageView
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:scaleType="centerCrop"
- android:layout_marginRight="24dp"
- android:layout_marginLeft="24dp"
- android:src="@drawable/ic_add"/>
-
- <TextView
- android:id="@+id/add_new_sound_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:text="@null"
- android:textColor="?android:attr/colorAccent"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:maxLines="3"
- android:gravity="center_vertical"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:drawablePadding="20dp"
- android:ellipsize="marquee"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
deleted file mode 100644
index 787f92e..0000000
--- a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<androidx.recyclerview.widget.RecyclerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/recycler_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
deleted file mode 100644
index 7efd911..0000000
--- a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.google.android.material.tabs.TabLayout
- android:id="@+id/tabLayout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <androidx.viewpager2.widget.ViewPager2
- android:id="@+id/masterViewPager"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
deleted file mode 100644
index 36ac93e..0000000
--- a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.soundpicker.CheckedListItem
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
- android:clickable="true">
-
- <CheckedTextView
- android:id="@+id/checked_text_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorAlertDialogListItem"
- android:gravity="center_vertical"
- android:paddingStart="20dp"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:drawableStart="?android:attr/listChoiceIndicatorSingle"
- android:drawablePadding="20dp"
- android:ellipsize="marquee"
- android:layout_toLeftOf="@+id/work_icon"
- android:maxLines="3"/>
-
- <ImageView
- android:id="@id/work_icon"
- android:layout_width="18dp"
- android:layout_height="18dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:scaleType="centerCrop"
- android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_notification_sound.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_ringtone.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
deleted file mode 100644
index 4e237a2..0000000
--- a/packages/SoundPicker2/res/values/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. Do not translate.
-
- NOTE: The naming convention is "config_camelCaseValue". -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
- ringtone will be automatically selected when the picker is closed. -->
- <bool name="config_showOkCancelButtons">true</bool>
-</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
deleted file mode 100644
index ab7b95a..0000000
--- a/packages/SoundPicker2/res/values/strings.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Choice in the ringtone picker. If chosen, the default ringtone will be used. -->
- <string name="ringtone_default">Default ringtone</string>
-
- <!-- Choice in the notification sound picker. If chosen, the default notification sound will be
- used. -->
- <string name="notification_sound_default">Default notification sound</string>
-
- <!-- Choice in the alarm sound picker. If chosen, the default alarm sound will be used. -->
- <string name="alarm_sound_default">Default alarm sound</string>
-
- <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
- <string name="add_ringtone_text">Add ringtone</string>
- <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
- <string name="add_alarm_text">Add alarm</string>
- <!-- Text for the RingtonePicker item that allows adding a new notification. -->
- <string name="add_notification_text">Add notification</string>
- <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
- <string name="delete_ringtone_text">Delete</string>
- <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
- <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
- <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
- <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
-
- <!-- Text for the name of the app. [CHAR LIMIT=12] -->
- <string name="app_label">Sounds</string>
-
- <string name="empty_list">The list is empty</string>
- <string name="sound_page_title">Sound</string>
- <string name="vibration_page_title">Vibration</string>
-</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
deleted file mode 100644
index d22d9c4..0000000
--- a/packages/SoundPicker2/res/values/styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
- </style>
-
-</resources>
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
deleted file mode 100644
index 4fc2a86..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Activity;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import java.util.Objects;
-
-/**
- * Base class for generic picker fragments.
- *
- * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
- * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
- * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
- * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
- * one fragment and vibration selected by another).
- */
-@AndroidEntryPoint(Fragment.class)
-public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
- RingtoneListViewAdapter.Callbacks {
-
- private static final String TAG = "BasePickerFragment";
- private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
- private boolean mIsManagedProfile;
- private Drawable mWorkIconDrawable;
-
- protected RingtoneListViewAdapter mRingtoneListViewAdapter;
- protected RecyclerView mRecyclerView;
- protected RingtonePickerViewModel.Config mPickerConfig;
- protected RingtonePickerViewModel mRingtonePickerViewModel;
- protected RingtoneListHandler.Config mRingtoneListConfig;
- protected RingtoneListHandler mRingtoneListHandler;
-
- public BasePickerFragment() {
- super(R.layout.fragment_ringtone_picker);
- }
-
- @Override
- public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- mRingtoneListHandler = getRingtoneListHandler();
- mRecyclerView = view.requireViewById(R.id.recycler_view);
-
- mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
- mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
-
- mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
- mPickerConfig.userId);
-
- mRingtoneListViewAdapter = createRingtoneListViewAdapter();
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(mRingtoneListViewAdapter);
- mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
- setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
- prepareRecyclerView(mRecyclerView);
- }
-
- @Override
- public boolean isWorkRingtone(int position) {
- if (!mIsManagedProfile) {
- return false;
- }
-
- /*
- * Display the work icon if the ringtone belongs to a work profile. We
- * can tell that a ringtone belongs to a work profile if the picker user
- * is a managed profile, the ringtone Uri is in external storage, and
- * either the uri has no user id or has the id of the picker user
- */
- Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
- int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
- mPickerConfig.userId);
- Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
- return uriUserId == mPickerConfig.userId
- && uriWithoutUserId.toString().startsWith(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
- }
-
- @Override
- public Drawable getWorkIconDrawable() {
- if (mWorkIconDrawable == null) {
- mWorkIconDrawable = requireActivity().getPackageManager()
- .getUserBadgeForDensityNoBackground(
- UserHandle.of(mPickerConfig.userId), /* density= */ -1);
- }
-
- return mWorkIconDrawable;
- }
-
- @Override
- public void onRingtoneSelected(int position) {
- setSelectedItem(position);
-
- // In the buttonless (watch-only) version, preemptively set our result since
- // we won't have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithSelectedRingtone();
- }
-
- // Play clip
- mRingtonePickerViewModel.playRingtone();
- }
-
- @Override
- public void onAddRingtoneSelected() {
- addRingtoneAsync();
- }
-
- /**
- * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
- * selected item in the list.
- * @param recyclerView The recyclerview that contains the list of displayed items.
- */
- protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
- // Reset the static item count, as this method can be called multiple times
- mRingtoneListHandler.resetFixedItems();
-
- if (mRingtoneListConfig.hasDefaultItem) {
- int defaultItemPos = addDefaultRingtoneItem();
-
- if (getSelectedItem() < 0
- && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
- setSelectedItem(defaultItemPos);
- }
- }
-
- if (mRingtoneListConfig.hasSilentItem) {
- int silentItemPos = addSilentItem();
-
- // The 'Silent' item should use a null Uri
- if (getSelectedItem() < 0
- && mRingtoneListConfig.initialSelectedUri == null) {
- setSelectedItem(silentItemPos);
- }
- }
-
- if (getSelectedItem() < 0) {
- setSelectedItem(mRingtoneListHandler.getRingtonePosition(
- mRingtoneListConfig.initialSelectedUri));
- }
-
- // In the buttonless (watch-only) version, preemptively set our result since we won't
- // have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithSelectedRingtone();
- }
-
- addNewRingtoneItem();
-
- // Enable context menu in ringtone items
- registerForContextMenu(recyclerView);
- }
-
- /**
- * Returns the fragment's sound/vibration list handler.
- * @return The ringtone list handler.
- */
- protected abstract RingtoneListHandler getRingtoneListHandler();
-
- /**
- * Starts the process to add a new ringtone to the list of ringtones asynchronously.
- * Currently, only works for adding sound files.
- */
- protected abstract void addRingtoneAsync();
-
- /**
- * Adds an item to the end of the list that can be used to add new ringtones to the list.
- * Currently, only works for adding sound files.
- */
- protected abstract void addNewRingtoneItem();
-
- protected int getSelectedItem() {
- return mRingtoneListHandler.getSelectedItemPosition();
- }
-
- /**
- * Returns the selected URI to the caller activity.
- */
- protected void setSuccessResultWithSelectedRingtone() {
- requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
- mRingtonePickerViewModel.getSelectedRingtoneUri()));
- }
-
- /**
- * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
- * @return The created RingtoneListViewAdapter.
- */
- protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
- LocalizedCursor cursor = new LocalizedCursor(
- mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
- return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
- }
-
- /**
- * Sets the selected item in the list and scroll to the position in the recyclerview.
- * @param pos the position of the selected item in the list.
- */
- protected void setSelectedItem(int pos) {
- Objects.requireNonNull(mRingtoneListViewAdapter);
- mRingtoneListHandler.setSelectedItemPosition(pos);
- mRingtoneListViewAdapter.setSelectedItem(pos);
- mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
- mRecyclerView.scrollToPosition(pos);
- }
-
- /**
- * Adds a fixed item to the fixed items list . A fixed item is one that is not from
- * the RingtoneManager.
- *
- * @param textResId The resource ID of the text for the item.
- * @return The index of the inserted fixed item in the adapter.
- */
- protected int addFixedItem(int textResId) {
- return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
- }
-
- /**
- * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
- * selected item position to match the new position of the chosen ringtone.
- * <p>
- * This should only need to happen after adding or removing a ringtone.
- */
- protected void requeryForAdapter() {
- mRingtonePickerViewModel.reinit();
- // Refresh and set a new cursor, and closing the old one.
- mRingtoneListViewAdapter = createRingtoneListViewAdapter();
- mRecyclerView.setAdapter(mRingtoneListViewAdapter);
- prepareRecyclerView(mRecyclerView);
-
- // Update selected item location.
- for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
- if (mRingtoneListViewAdapter.getItemId(i)
- == mRingtoneListHandler.getSelectedItemId()) {
- setSelectedItem(i);
- return;
- }
- }
-
- // If selected item is still unknown, then set it to the default item, if available.
- // If it's not available, then attempt to set it to the silent item in the list.
- int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
-
- if (selectedPosition < 0) {
- selectedPosition = mRingtoneListHandler.getSilentItemPosition();
- }
-
- setSelectedItem(selectedPosition);
- }
-
- private int addDefaultRingtoneItem() {
- int defaultItemPosInAdapter = addFixedItem(
- RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- mPickerConfig.ringtoneType));
- int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
-
- if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
- Log.wtf(TAG, "Default item position in adapter and list handler must match.");
- return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
- }
-
- return defaultItemPosInListHandler;
- }
-
- private int addSilentItem() {
- int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
- int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
-
- if (silentItemPosInAdapter != silentItemPosInListHandler) {
- Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
- return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
- }
-
- return silentItemPosInListHandler;
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
deleted file mode 100644
index 819ae98..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.CheckedTextView;
-import android.widget.RelativeLayout;
-
-/**
- * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
- * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
- * name if the ringtone belongs to a work profile.
- */
-public class CheckedListItem extends RelativeLayout implements Checkable {
-
- public CheckedListItem(Context context) {
- super(context);
- }
-
- public CheckedListItem(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- public void setChecked(boolean checked) {
- getCheckedTextView().setChecked(checked);
- }
-
- @Override
- public boolean isChecked() {
- return getCheckedTextView().isChecked();
- }
-
- @Override
- public void toggle() {
- getCheckedTextView().toggle();
- }
-
- private CheckedTextView getCheckedTextView() {
- return (CheckedTextView) findViewById(R.id.checked_text_view);
- }
-
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
deleted file mode 100644
index afdbf05..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.Executors;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link ListeningExecutorService}.
- */
-@Singleton
-public class ListeningExecutorServiceFactory {
-
- @Inject
- ListeningExecutorServiceFactory() {
- }
-
- /**
- * Returns a single thread {@link ListeningExecutorService}.
- *
- */
- public ListeningExecutorService createSingleThreadExecutor() {
- return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
deleted file mode 100644
index 83d04a3..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.util.Log;
-import android.util.TypedValue;
-
-import androidx.annotation.Nullable;
-
-import java.util.Locale;
-import java.util.regex.Pattern;
-
-/**
- * A cursor wrapper class mainly used to guarantee getting a ringtone title
- */
-final class LocalizedCursor extends CursorWrapper {
-
- private static final String TAG = "LocalizedCursor";
- private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
-
- private final int mTitleIndex;
- private final Resources mResources;
- private final Pattern mSanitizePattern;
- private final String mNamePrefix;
-
- LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
- super(cursor);
- mTitleIndex = mCursor.getColumnIndex(columnLabel);
- mResources = resources;
- mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
- if (mTitleIndex == -1) {
- Log.e(TAG, "No index for column " + columnLabel);
- mNamePrefix = null;
- } else {
- mNamePrefix = buildNamePrefix(mResources);
- }
- }
-
- /**
- * Builds the prefix for the name of the resource to look up.
- * The format is: "ResourcePackageName::ResourceTypeName/" (the type name is expected to be
- * "string" but let's not hardcode it).
- * Here we use an existing resource "notification_sound_default" which is always expected to be
- * found.
- *
- * @param resources Application's resources
- * @return the built name prefix, or null if failed to build.
- */
- @Nullable
- private static String buildNamePrefix(Resources resources) {
- try {
- return String.format("%s:%s/%s",
- resources.getResourcePackageName(R.string.notification_sound_default),
- resources.getResourceTypeName(R.string.notification_sound_default),
- SOUND_NAME_RES_PREFIX);
- } catch (Resources.NotFoundException e) {
- Log.e(TAG, "Failed to build the prefix for the name of the resource.", e);
- }
-
- return null;
- }
-
- /**
- * Process resource name to generate a valid resource name.
- *
- * @return a non-null String
- */
- private String sanitize(String input) {
- if (input == null) {
- return "";
- }
- return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(Locale.ROOT);
- }
-
- @Override
- public String getString(int columnIndex) {
- final String defaultName = mCursor.getString(columnIndex);
- if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
- return defaultName;
- }
- TypedValue value = new TypedValue();
- try {
- // the name currently in the database is used to derive a name to match
- // against resource names in this package
- mResources.getValue(mNamePrefix + sanitize(defaultName), value,
- /* resolveRefs= */ false);
- } catch (Resources.NotFoundException e) {
- Log.d(TAG, "Failed to get localized string. Using default string instead.", e);
- return defaultName;
- }
- if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
- Log.d(TAG, String.format("Replacing name %s with %s",
- defaultName, value.string.toString()));
- return value.string.toString();
- } else {
- Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
- return defaultName;
- }
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
deleted file mode 100644
index 6817f53..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link Ringtone}.
- */
-@Singleton
-public class RingtoneFactory {
-
- private final Context mApplicationContext;
-
- @Inject
- RingtoneFactory(@ApplicationContext Context applicationContext) {
- mApplicationContext = applicationContext;
- }
-
- /**
- * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
- *
- * @param uri The URI used to build the {@link Ringtone}.
- * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
- * and settings when playing the ringtone.
- * @return the built {@link Ringtone}.
- */
- public Ringtone create(Uri uri, int audioAttributesFlags) {
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setFlags(audioAttributesFlags)
- .build();
- return RingtoneManager.getRingtone(mApplicationContext, uri,
- /* volumeShaperConfig= */ null, audioAttributes);
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
deleted file mode 100644
index bb38e0e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import javax.inject.Inject;
-
-/**
- * Handles ringtone list state and actions. This includes keeping track of the selected item,
- * ringtone manager cursor and added items to the list.
- */
-public class RingtoneListHandler {
-
- // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
- // while empty ones don't (Potentially this might be due to empty URIs being perceived as
- // malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
- // once they become available.
- static final Uri SILENT_URI = Uri.EMPTY;
- static final int ITEM_POSITION_UNKNOWN = -1;
-
- private static final String TAG = "RingtoneListHandler";
-
- /** The position in the list of the 'Silent' item. */
- private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
- /** The position in the list of the 'Default' item. */
- private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
- /** The number of fixed items in the list. */
- private int mFixedItemCount;
- /**
- * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
- */
- private long mSelectedItemId = -1;
- private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
-
- private RingtoneManager mRingtoneManager;
- private Config mRingtoneListConfig;
- private Cursor mRingtoneCursor;
-
- /**
- * Holds immutable info on the ringtone list that is displayed.
- */
- static final class Config {
- /**
- * Whether this list has the 'Default' item.
- */
- public final boolean hasDefaultItem;
- /**
- * The Uri to play when the 'Default' item is clicked.
- */
- public final Uri uriForDefaultItem;
- /**
- * Whether this list has the 'Silent' item.
- */
- public final boolean hasSilentItem;
- /**
- * The initially selected uri in the list.
- */
- public final Uri initialSelectedUri;
-
- Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
- Uri initialSelectedUri) {
- this.hasDefaultItem = hasDefaultItem;
- this.uriForDefaultItem = uriForDefaultItem;
- this.hasSilentItem = hasSilentItem;
- this.initialSelectedUri = initialSelectedUri;
- }
- }
-
- @Inject
- RingtoneListHandler() {
- }
-
- void init(@NonNull Config ringtoneListConfig,
- @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
- mRingtoneManager = requireNonNull(ringtoneManager);
- mRingtoneListConfig = requireNonNull(ringtoneListConfig);
- mRingtoneCursor = requireNonNull(ringtoneCursor);
- }
-
- Config getRingtoneListConfig() {
- return mRingtoneListConfig;
- }
-
- Cursor getRingtoneCursor() {
- requireInitCalled();
- return mRingtoneCursor;
- }
-
- Uri getRingtoneUri(int position) {
- if (position < 0) {
- Log.w(TAG, "Selected item position is unknown.");
- // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
- // We return SILENT_URI for this case.
- return SILENT_URI;
- } else if (position == mDefaultItemPosition) {
- // Use the default Uri that they originally gave us.
- return mRingtoneListConfig.uriForDefaultItem;
- } else if (position == mSilentItemPosition) {
- // Use SILENT_URI for the 'Silent' item.
- return SILENT_URI;
- } else {
- requireInitCalled();
- return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
- }
- }
-
- int getRingtonePosition(Uri uri) {
- requireInitCalled();
- return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
- }
-
- void resetFixedItems() {
- mFixedItemCount = 0;
- mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
- mSilentItemPosition = ITEM_POSITION_UNKNOWN;
- }
-
- int addDefaultItem() {
- if (mDefaultItemPosition < 0) {
- mDefaultItemPosition = addFixedItem();
- }
- return mDefaultItemPosition;
- }
-
- int getDefaultItemPosition() {
- return mDefaultItemPosition;
- }
-
- int addSilentItem() {
- if (mSilentItemPosition < 0) {
- mSilentItemPosition = addFixedItem();
- }
- return mSilentItemPosition;
- }
-
- public int getSilentItemPosition() {
- return mSilentItemPosition;
- }
-
- int getSelectedItemPosition() {
- return mSelectedItemPosition;
- }
-
- void setSelectedItemPosition(int selectedItemPosition) {
- mSelectedItemPosition = selectedItemPosition;
- }
-
- void setSelectedItemId(long selectedItemId) {
- mSelectedItemId = selectedItemId;
- }
-
- long getSelectedItemId() {
- return mSelectedItemId;
- }
-
- @Nullable
- Uri getSelectedRingtoneUri() {
- return getRingtoneUri(mSelectedItemPosition);
- }
-
- /**
- * Maps the item position in the list, to its equivalent position in the RingtoneManager.
- *
- * @param itemPosition the position of item in the list.
- * @return position of the item in the RingtoneManager.
- */
- private int mapListPositionToRingtonePosition(int itemPosition) {
- // If the manager position is less than add items, then return that.
- if (itemPosition < mFixedItemCount) return itemPosition;
-
- return itemPosition - mFixedItemCount;
- }
-
- /**
- * Maps the item position in the RingtoneManager, to its equivalent position in the list.
- *
- * @param itemPosition the position of the item in the RingtoneManager.
- * @return position of the item in the list.
- */
- private int mapRingtonePositionToListPosition(int itemPosition) {
- // If the manager position is less than add items, then return that.
- if (itemPosition < 0) return itemPosition;
-
- return itemPosition + mFixedItemCount;
- }
-
- /**
- * Increments the number of added fixed items and returns the index of the newest added item.
- * @return index of the newest added fixed item.
- */
- private int addFixedItem() {
- return mFixedItemCount++;
- }
-
- private void requireInitCalled() {
- requireNonNull(mRingtoneManager);
- requireNonNull(mRingtoneCursor);
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
deleted file mode 100644
index 4ca8943..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static com.android.internal.widget.RecyclerView.NO_ID;
-
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckedTextView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * The adapter presents a list of ringtones which may include fixed item in the list and an action
- * button at the end.
- *
- * The adapter handles three different types of items:
- * <ul>
- * <li>FIXED: Fixed items are items added to the top of the list. These items can not be modified
- * and their position will never change.
- * <li>DYNAMIC: Dynamic items are items from the ringtone manager. These items can be modified
- * and their position can change.
- * <li>FOOTER: A footer item is an added button to the end of the list. This item can be clicked
- * but not selected and its position will never change.
- * </ul>
- */
-final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
- private static final int VIEW_TYPE_FIXED_ITEM = 0;
- private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
- private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
- private final Cursor mCursor;
- private final List<Integer> mFixedItemTitles;
- private final Callbacks mCallbacks;
- private final int mRowIDColumn;
- private int mSelectedItem = -1;
- @StringRes private Integer mAddRingtoneItemTitle;
-
- /** Provides callbacks for the adapter. */
- interface Callbacks {
- void onRingtoneSelected(int position);
- void onAddRingtoneSelected();
- boolean isWorkRingtone(int position);
- Drawable getWorkIconDrawable();
- }
-
- RingtoneListViewAdapter(Cursor cursor,
- Callbacks callbacks) {
- mCursor = cursor;
- mCallbacks = callbacks;
- mFixedItemTitles = new ArrayList<>();
- mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
- setHasStableIds(true);
- }
-
- void setSelectedItem(int position) {
- notifyItemChanged(mSelectedItem);
- mSelectedItem = position;
- notifyItemChanged(mSelectedItem);
- }
-
- /**
- * Adds title to the fixed items list and returns the index of the newest added item.
- * @param textResId the title to add to the fixed items list.
- * @return The index of the newest added item in the fixed items list.
- */
- int addTitleForFixedItem(@StringRes int textResId) {
- mFixedItemTitles.add(textResId);
- notifyItemInserted(mFixedItemTitles.size() - 1);
- return mFixedItemTitles.size() - 1;
- }
-
- void addTitleForAddRingtoneItem(@StringRes int textResId) {
- mAddRingtoneItemTitle = textResId;
- notifyItemInserted(getItemCount() - 1);
- }
-
- @NotNull
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
- if (viewType == VIEW_TYPE_FIXED_ITEM) {
- View fixedItemView = inflater.inflate(
- com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
- false);
-
- return new FixedItemViewHolder(fixedItemView, mCallbacks);
- }
-
- if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
- View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
-
- return new AddRingtoneItemViewHolder(addRingtoneItemView,
- mCallbacks);
- }
-
- View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
-
- return new DynamicItemViewHolder(view, mCallbacks);
- }
-
- @Override
- public void onBindViewHolder(@NotNull RecyclerView.ViewHolder holder, int position) {
- if (holder instanceof FixedItemViewHolder) {
- FixedItemViewHolder viewHolder = (FixedItemViewHolder) holder;
-
- viewHolder.onBind(mFixedItemTitles.get(position),
- /* isChecked= */ position == mSelectedItem);
- return;
- }
- if (holder instanceof AddRingtoneItemViewHolder) {
- AddRingtoneItemViewHolder viewHolder = (AddRingtoneItemViewHolder) holder;
-
- viewHolder.onBind(mAddRingtoneItemTitle);
- return;
- }
-
- if (!(holder instanceof DynamicItemViewHolder)) {
- throw new IllegalArgumentException("holder type is not supported");
- }
-
- DynamicItemViewHolder viewHolder = (DynamicItemViewHolder) holder;
- int pos = position - mFixedItemTitles.size();
- if (!mCursor.moveToPosition(pos)) {
- throw new IllegalStateException("Could not move cursor to position: " + pos);
- }
-
- Drawable workIcon = (mCallbacks != null)
- && mCallbacks.isWorkRingtone(position)
- ? mCallbacks.getWorkIconDrawable() : null;
-
- viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
- /* isChecked= */ position == mSelectedItem, workIcon);
- }
-
- @Override
- public int getItemViewType(int position) {
- if (!mFixedItemTitles.isEmpty() && position < mFixedItemTitles.size()) {
- return VIEW_TYPE_FIXED_ITEM;
- }
- if (mAddRingtoneItemTitle != null && position == getItemCount() - 1) {
- return VIEW_TYPE_ADD_RINGTONE_ITEM;
- }
-
- return VIEW_TYPE_DYNAMIC_ITEM;
- }
-
- @Override
- public int getItemCount() {
- int itemCount = mFixedItemTitles.size() + mCursor.getCount();
-
- if (mAddRingtoneItemTitle != null) {
- itemCount++;
- }
-
- return itemCount;
- }
-
- @Override
- public long getItemId(int position) {
- int itemViewType = getItemViewType(position);
- if (itemViewType == VIEW_TYPE_FIXED_ITEM) {
- // Since the item is a fixed item, then we can use the position as a stable ID
- // since the order of the fixed items should never change.
- return position;
- }
- if (itemViewType == VIEW_TYPE_DYNAMIC_ITEM && mCursor != null
- && mCursor.moveToPosition(position - mFixedItemTitles.size())
- && mRowIDColumn != -1) {
- return mCursor.getLong(mRowIDColumn) + mFixedItemTitles.size();
- }
-
- // The position is either invalid or the item is the add ringtone item view, so no stable
- // ID is returned. Add ringtone item view cannot be selected and only include an action
- // buttons.
- return NO_ID;
- }
-
- private static class DynamicItemViewHolder extends RecyclerView.ViewHolder {
- private final CheckedTextView mTitleTextView;
- private final ImageView mWorkIcon;
-
- DynamicItemViewHolder(View itemView, Callbacks listener) {
- super(itemView);
-
- mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
- mWorkIcon = itemView.requireViewById(R.id.work_icon);
- itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
- }
-
- void onBind(String title, boolean isChecked, Drawable workIcon) {
- mTitleTextView.setText(title);
- mTitleTextView.setChecked(isChecked);
-
- if (workIcon == null) {
- mWorkIcon.setVisibility(View.GONE);
- } else {
- mWorkIcon.setImageDrawable(workIcon);
- mWorkIcon.setVisibility(View.VISIBLE);
- }
- }
- }
-
- private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
- private final CheckedTextView mTitleTextView;
-
- FixedItemViewHolder(View itemView, Callbacks listener) {
- super(itemView);
-
- mTitleTextView = (CheckedTextView) itemView;
- itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
- }
-
- void onBind(@StringRes int title, boolean isChecked) {
- Objects.requireNonNull(mTitleTextView);
-
- mTitleTextView.setText(title);
- mTitleTextView.setChecked(isChecked);
- }
- }
-
- private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
- private final TextView mTitleTextView;
-
- AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
- super(itemView);
-
- mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
- itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
- }
-
- void onBind(@StringRes int title) {
- mTitleTextView.setText(title);
- }
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
deleted file mode 100644
index f08eb24..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Context;
-import android.media.RingtoneManager;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link RingtoneManager}.
- */
-@Singleton
-public class RingtoneManagerFactory {
-
- private final Context mApplicationContext;
-
- @Inject
- RingtoneManagerFactory(@ApplicationContext Context applicationContext) {
- mApplicationContext = applicationContext;
- }
-
- /**
- * Creates a new {@link RingtoneManager} and returns it.
- *
- * @return a {@link RingtoneManager}
- */
- public RingtoneManager create() {
- return new RingtoneManager(mApplicationContext, /* includeParentRingtones */ true);
- }
-}
-
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
deleted file mode 100644
index b94ebeb..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Service;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.provider.MediaStore;
-import android.provider.Settings.System;
-import android.util.Log;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Service to copy and set customization of default sounds
- */
-public class RingtoneOverlayService extends Service {
- private static final String TAG = "RingtoneOverlayService";
- private static final boolean DEBUG = false;
-
- @Override
- public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
- AsyncTask.execute(() -> {
- updateRingtones();
- stopSelf();
- });
-
- // Try again later if we are killed before we finish.
- return Service.START_REDELIVER_INTENT;
- }
-
- @Override
- public IBinder onBind(@Nullable final Intent intent) {
- return null;
- }
-
- private void updateRingtones() {
- copyResourceAndSetAsSound(R.raw.default_ringtone,
- System.RINGTONE, Environment.DIRECTORY_RINGTONES);
- copyResourceAndSetAsSound(R.raw.default_notification_sound,
- System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
- copyResourceAndSetAsSound(R.raw.default_alarm_alert,
- System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
- }
-
- /* If the resource contains any data, copy a resource to the file system, scan it, and set the
- * file URI as the default for a sound. */
- private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
- @NonNull final String subPath) {
- final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
- if (!destDir.exists() && !destDir.mkdirs()) {
- Log.e(TAG, "can't create " + destDir.getAbsolutePath());
- return;
- }
-
- final File dest = new File(destDir, "default_" + name + ".ogg");
- try (
- InputStream is = getResources().openRawResource(id);
- FileOutputStream os = new FileOutputStream(dest);
- ) {
- if (is.available() > 0) {
- FileUtils.copy(is, os);
- final Uri uri = scanFile(dest);
- if (uri != null) {
- set(name, uri);
- }
- } else {
- // TODO Shall we remove any former copied resource in this case and unset
- // the defaults if we use this event a second time to clear the data?
- if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
- }
- } catch (IOException e) {
- Log.e(TAG, "Unable to open resource for " + name + ": " + e);
- }
- }
-
- private Uri scanFile(@NonNull final File file) {
- return MediaStore.scanFile(getContentResolver(), file);
- }
-
- private void set(@NonNull final String name, @NonNull final Uri uri) {
- final Uri settingUri = System.getUriFor(name);
- RingtoneManager.setActualDefaultRingtoneUri(this,
- RingtoneManager.getDefaultType(settingUri), uri);
- System.putInt(getContentResolver(), name + "_set", 1);
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
deleted file mode 100644
index 90a14f9..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2007 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.soundpicker;
-
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-/**
- * The {@link RingtonePickerActivity} allows the user to choose one from all of the
- * available ringtones. The chosen ringtone's URI will be persisted as a string.
- *
- * @see RingtoneManager#ACTION_RINGTONE_PICKER
- */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
-
- private static final String TAG = "RingtonePickerActivity";
- // TODO: Use the extra keys from RingtoneManager once they're added.
- private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
- private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
- private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
- private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
- private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
- private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
-
- private RingtonePickerViewModel mRingtonePickerViewModel;
- private int mAttributesFlags;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_ringtone_picker);
-
- mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
-
- Intent intent = getIntent();
- /**
- * Id of the user to which the ringtone picker should list the ringtones
- */
- int pickerUserId = UserHandle.myUserId();
-
- // Get the types of ringtones to show
- int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
- RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
-
- // AudioAttributes flags
- mAttributesFlags |= intent.getIntExtra(
- RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
- 0 /*defaultValue == no flags*/);
-
- boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
- String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
- if (title == null) {
- title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
- }
- String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
- RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
- ringtonePickerCategory);
-
- RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
- ringtoneType);
- RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
- RingtonePickerViewModel.Config pickerConfig =
- new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
- showOkCancelButtons, mAttributesFlags, pickerType);
-
- mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
- if (savedInstanceState == null) {
- TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
- if (prev != null) {
- ft.remove(prev);
- }
- ft.addToBackStack(null);
- dialogFragment.show(ft, TabbedDialogFragment.TAG);
- }
-
- // The volume keys will control the stream that we are choosing a ringtone for
- setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
- }
-
- private RingtoneListHandler.Config getSoundListConfig(
- RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
- if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
- && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
- // This ringtone picker does not require a sound picker.
- return null;
- }
-
- // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
- boolean hasDefaultSoundItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
- // The Uri to play when the 'Default' sound item is clicked.
- Uri uriForDefaultSoundItem =
- intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
- if (uriForDefaultSoundItem == null) {
- uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
- }
-
- // Get whether this list has the 'Silent' sound item.
- boolean hasSilentSoundItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
- // AudioAttributes flags
- mAttributesFlags |= intent.getIntExtra(
- RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
- 0 /*defaultValue == no flags*/);
-
- // Get the sound URI whose list item should have a checkmark
- Uri existingSoundUri = intent
- .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
-
- return new RingtoneListHandler.Config(hasDefaultSoundItem,
- uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
- }
-
- private RingtoneListHandler.Config getVibrationListConfig(
- RingtonePickerViewModel.PickerType pickerType, Intent intent) {
- if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
- && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
- // This ringtone picker does not require a vibration picker.
- return null;
- }
-
- // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
- boolean hasDefaultVibrationItem =
- intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
-
- // The Uri to play when the 'Default' vibration item is clicked.
- Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
-
- // Get whether this list has the 'Silent' vibration item.
- boolean hasSilentVibrationItem =
- intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
-
- // Get the vibration URI whose list item should have a checkmark
- Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
- return new RingtoneListHandler.Config(
- hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
- existingVibrationUri);
- }
-
- @Override
- public void onDestroy() {
- mRingtonePickerViewModel.cancelPendingAsyncTasks();
- super.onDestroy();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mRingtonePickerViewModel.onStop(isChangingConfigurations());
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mRingtonePickerViewModel.onPause(isChangingConfigurations());
- }
-
- /**
- * Maps the ringtone picker category to the appropriate PickerType.
- * If the category is null or the feature is still not released, then it defaults to sound
- * picker.
- *
- * @param category the ringtone picker category.
- * @return the corresponding picker type.
- */
- private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
- if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
- }
-
- switch (category) {
- case "android.intent.category.RINGTONE_PICKER_RINGTONE":
- return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
- case "android.intent.category.RINGTONE_PICKER_SOUND":
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
- case "android.intent.category.RINGTONE_PICKER_VIBRATION":
- return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
- default:
- Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
- }
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
deleted file mode 100644
index 48fd4fe..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Application;
-
-import dagger.hilt.android.HiltAndroidApp;
-
-/**
- * The main application class for the project.
- */
-@HiltAndroidApp(Application.class)
-public class RingtonePickerApplication extends Hilt_RingtonePickerApplication {
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
deleted file mode 100644
index 2c09711..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-
-import dagger.hilt.android.lifecycle.HiltViewModel;
-
-import java.io.IOException;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * A view model which holds immutable info about the picker state and means to retrieve and play
- * currently selected ringtones.
- */
-@HiltViewModel
-public final class RingtonePickerViewModel extends ViewModel {
-
- static final int RINGTONE_TYPE_UNKNOWN = -1;
-
- /**
- * Keep the currently playing ringtone around when changing orientation, so that it
- * can be stopped later, after the activity is recreated.
- */
- @VisibleForTesting
- static Ringtone sPlayingRingtone;
-
- private static final String TAG = "RingtonePickerViewModel";
-
- private final RingtoneManagerFactory mRingtoneManagerFactory;
- private final RingtoneFactory mRingtoneFactory;
- private final RingtoneListHandler mSoundListHandler;
- private final RingtoneListHandler mVibrationListHandler;
- private final ListeningExecutorService mListeningExecutorService;
-
- private RingtoneManager mRingtoneManager;
-
- /**
- * The ringtone that's currently playing.
- */
- private Ringtone mCurrentRingtone;
-
- private Config mPickerConfig;
-
- private ListenableFuture<Uri> mAddCustomRingtoneFuture;
-
- public enum PickerType {
- RINGTONE_PICKER,
- SOUND_PICKER,
- VIBRATION_PICKER
- }
-
- /**
- * Holds immutable info on the picker that should be displayed.
- */
- static final class Config {
- public final String title;
- /**
- * Id of the user to which the ringtone picker should list the ringtones.
- */
- public final int userId;
- /**
- * Ringtone type.
- */
- public final int ringtoneType;
- /**
- * AudioAttributes flags.
- */
- public final int audioAttributesFlags;
- /**
- * In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
- */
- public final boolean showOkCancelButtons;
-
- public final PickerType mPickerType;
-
- Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
- int audioAttributesFlags, PickerType pickerType) {
- this.title = title;
- this.userId = userId;
- this.ringtoneType = ringtoneType;
- this.showOkCancelButtons = showOkCancelButtons;
- this.audioAttributesFlags = audioAttributesFlags;
- this.mPickerType = pickerType;
- }
- }
-
- @Inject
- RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
- RingtoneFactory ringtoneFactory,
- ListeningExecutorServiceFactory listeningExecutorServiceFactory,
- RingtoneListHandler soundListHandler,
- RingtoneListHandler vibrationListHandler) {
- mRingtoneManagerFactory = ringtoneManagerFactory;
- mRingtoneFactory = ringtoneFactory;
- mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
- mSoundListHandler = soundListHandler;
- mVibrationListHandler = vibrationListHandler;
- }
-
- @StringRes
- static int getTitleByType(int ringtoneType) {
- switch (ringtoneType) {
- case RingtoneManager.TYPE_ALARM:
- return com.android.internal.R.string.ringtone_picker_title_alarm;
- case RingtoneManager.TYPE_NOTIFICATION:
- return com.android.internal.R.string.ringtone_picker_title_notification;
- default:
- return com.android.internal.R.string.ringtone_picker_title;
- }
- }
-
- static Uri getDefaultItemUriByType(int ringtoneType) {
- switch (ringtoneType) {
- case RingtoneManager.TYPE_ALARM:
- return Settings.System.DEFAULT_ALARM_ALERT_URI;
- case RingtoneManager.TYPE_NOTIFICATION:
- return Settings.System.DEFAULT_NOTIFICATION_URI;
- default:
- return Settings.System.DEFAULT_RINGTONE_URI;
- }
- }
-
- @StringRes
- static int getAddNewItemTextByType(int ringtoneType) {
- switch (ringtoneType) {
- case RingtoneManager.TYPE_ALARM:
- return R.string.add_alarm_text;
- case RingtoneManager.TYPE_NOTIFICATION:
- return R.string.add_notification_text;
- default:
- return R.string.add_ringtone_text;
- }
- }
-
- @StringRes
- static int getDefaultRingtoneItemTextByType(int ringtoneType) {
- switch (ringtoneType) {
- case RingtoneManager.TYPE_ALARM:
- return R.string.alarm_sound_default;
- case RingtoneManager.TYPE_NOTIFICATION:
- return R.string.notification_sound_default;
- default:
- return R.string.ringtone_default;
- }
- }
-
- void init(@NonNull Config pickerConfig,
- RingtoneListHandler.Config soundListConfig,
- RingtoneListHandler.Config vibrationListConfig) {
- mRingtoneManager = mRingtoneManagerFactory.create();
- mPickerConfig = pickerConfig;
- if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
- mRingtoneManager.setType(mPickerConfig.ringtoneType);
- }
- if (soundListConfig != null) {
- mSoundListHandler.init(soundListConfig, mRingtoneManager,
- mRingtoneManager.getCursor());
- }
- if (vibrationListConfig != null) {
- // TODO: Switch to the vibration cursor, once the API is made available.
- mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
- mRingtoneManager.getCursor());
- }
- }
-
- /**
- * Re-initializes the view model which is required after updating any of the picker lists.
- * This could happen when adding a custom ringtone.
- */
- void reinit() {
- init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
- mVibrationListHandler.getRingtoneListConfig());
- }
-
- @NonNull
- Config getPickerConfig() {
- requireInitCalled();
- return mPickerConfig;
- }
-
- @NonNull
- RingtoneListHandler getSoundListHandler() {
- return mSoundListHandler;
- }
-
- @NonNull
- RingtoneListHandler getVibrationListHandler() {
- return mVibrationListHandler;
- }
-
- /**
- * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
- * picker does not show either sound or vibration, that portion of the URI will be null.
- *
- * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
- * yet.
- * @return Combined sound and vibration URI.
- */
- Uri getSelectedRingtoneUri() {
- // TODO: Combine sound and vibration URIs before returning.
- return mSoundListHandler.getSelectedRingtoneUri();
- }
-
- int getRingtoneStreamType() {
- requireInitCalled();
- return mRingtoneManager.inferStreamType();
- }
-
- void onPause(boolean isChangingConfigurations) {
- if (!isChangingConfigurations) {
- stopAnyPlayingRingtone();
- }
- }
-
- void onStop(boolean isChangingConfigurations) {
- if (isChangingConfigurations) {
- saveAnyPlayingRingtone();
- } else {
- stopAnyPlayingRingtone();
- }
- }
-
- /**
- * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
- * this is a sound or vibration only picker, then the other portion of the URI will be empty
- * and should not affect the played ringtone.
- *
- * Currently, we only use the sound URI to create the ringtone, since we still don't have the
- * API to retrieve the available vibrations list.
- */
- void playRingtone() {
- requireInitCalled();
- stopAnyPlayingRingtone();
-
- mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
- mPickerConfig.audioAttributesFlags);
-
- if (mCurrentRingtone != null) {
- mCurrentRingtone.play();
- }
- }
-
- /**
- * Cancels all pending async tasks.
- */
- void cancelPendingAsyncTasks() {
- if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
- mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
- }
- }
-
- /**
- * Adds an audio file to the list of ringtones asynchronously.
- * Any previous async tasks are canceled before start the new one.
- *
- * @param uri Uri of the file to be added as ringtone. Must be a media file.
- * @param type The type of the ringtone to be added.
- * @param callback The callback to invoke when the task is completed.
- * @param executor The executor to run the callback on when the task completes.
- */
- void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
- // Cancel any currently running add ringtone tasks before starting a new one
- cancelPendingAsyncTasks();
- mAddCustomRingtoneFuture = mListeningExecutorService.submit(
- () -> addRingtone(uri, type));
- Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
- }
-
- /**
- * Adds an audio file to the list of ringtones.
- *
- * @param uri Uri of the file to be added as ringtone. Must be a media file.
- * @param type The type of the ringtone to be added.
- * @return The Uri of the installed ringtone, which may be the {@code uri} if it
- * is already in ringtone storage. Or null if it failed to add the audio file.
- */
- @Nullable
- private Uri addRingtone(Uri uri, int type) throws IOException {
- requireInitCalled();
- return mRingtoneManager.addCustomExternalRingtone(uri, type);
- }
-
- private void saveAnyPlayingRingtone() {
- if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
- sPlayingRingtone = mCurrentRingtone;
- }
- mCurrentRingtone = null;
- }
-
- private void stopAnyPlayingRingtone() {
- if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
- sPlayingRingtone.stop();
- }
- sPlayingRingtone = null;
-
- if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
- mCurrentRingtone.stop();
- }
- mCurrentRingtone = null;
- }
-
- private void requireInitCalled() {
- requireNonNull(mRingtoneManager);
- requireNonNull(mPickerConfig);
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
deleted file mode 100644
index 6a34936..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2007 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.soundpicker;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class RingtoneReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
- initResourceRingtones(context);
- }
- }
-
- private void initResourceRingtones(Context context) {
- context.startService(
- new Intent(context, RingtoneOverlayService.class));
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
deleted file mode 100644
index a37191f..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.core.content.ContextCompat;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.google.common.util.concurrent.FutureCallback;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select sound or silent. It also includes the
- * ability to add custom sounds.
- */
-public class SoundPickerFragment extends BasePickerFragment {
-
- private static final String TAG = "SoundPickerFragment";
-
- private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
- @Override
- public void onSuccess(Uri ringtoneUri) {
- requeryForAdapter();
- }
-
- @Override
- public void onFailure(Throwable throwable) {
- Log.e(TAG, "Failed to add custom ringtone.", throwable);
- // Ringtone was not added, display error Toast
- Toast.makeText(requireActivity().getApplicationContext(),
- R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show();
- }
- };
-
- ActivityResultLauncher<Intent> mActivityResultLauncher = registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- new ActivityResultCallback<ActivityResult>() {
- @Override
- public void onActivityResult(ActivityResult result) {
- if (result.getResultCode() == Activity.RESULT_OK) {
- // There are no request codes
- Intent data = result.getData();
- mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
- mPickerConfig.ringtoneType,
- mAddCustomRingtoneCallback,
- // Causes the callback to be executed on the main thread.
- ContextCompat.getMainExecutor(
- requireActivity().getApplicationContext()));
- }
- }
- });
-
- @Override
- public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- super.onViewCreated(view, savedInstanceState);
- }
-
- @Override
- protected RingtoneListHandler getRingtoneListHandler() {
- return mRingtonePickerViewModel.getSoundListHandler();
- }
-
- @Override
- protected void addRingtoneAsync() {
- // The "Add new ringtone" item was clicked. Start a file picker intent to
- // select only audio files (MIME type "audio/*")
- final Intent chooseFile = getMediaFilePickerIntent();
- mActivityResultLauncher.launch(chooseFile);
- }
-
- @Override
- protected void addNewRingtoneItem() {
- // If external storage is available, add a button to install sounds from storage.
- if (resolvesMediaFilePicker()
- && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
- RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
- }
- }
-
- private boolean resolvesMediaFilePicker() {
- return getMediaFilePickerIntent().resolveActivity(requireActivity().getPackageManager())
- != null;
- }
-
- private Intent getMediaFilePickerIntent() {
- final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
- chooseFile.setType("audio/*");
- chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
- new String[]{"audio/*", "application/ogg"});
- return chooseFile;
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
deleted file mode 100644
index 50ea9d7..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static android.app.Activity.RESULT_CANCELED;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.viewpager2.widget.ViewPager2;
-
-import com.google.android.material.tabs.TabLayout;
-import com.google.android.material.tabs.TabLayoutMediator;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A dialog fragment with a sound and/or vibration tab based on the picker type.
- * <ul>
- * <li> Ringtone Pickers will display both sound and vibration tabs.
- * <li> Sound Pickers will only display the sound tab.
- * <li> Vibration Pickers will only display the vibration tab.
- * </ul>
- */
-@AndroidEntryPoint(DialogFragment.class)
-public class TabbedDialogFragment extends Hilt_TabbedDialogFragment {
-
- static final String TAG = "TabbedDialogFragment";
-
- private RingtonePickerViewModel mRingtonePickerViewModel;
-
- private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
- new ViewPager2.OnPageChangeCallback() {
- @Override
- public void onPageScrollStateChanged(int state) {
- super.onPageScrollStateChanged(state);
- if (state == ViewPager2.SCROLL_STATE_IDLE) {
- mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
- }
- }
- };
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity(),
- android.R.style.ThemeOverlay_Material_Dialog)
- .setTitle(mRingtonePickerViewModel.getPickerConfig().title);
- // Do not show OK/Cancel buttons in the buttonless (watch-only) version.
- if (mRingtonePickerViewModel.getPickerConfig().showOkCancelButtons) {
- dialogBuilder
- .setPositiveButton(getString(com.android.internal.R.string.ok),
- (dialog, whichButton) -> {
- setSuccessResultWithSelectedRingtone();
- requireActivity().finish();
- })
- .setNegativeButton(getString(com.android.internal.R.string.cancel),
- (dialog, whichButton) -> {
- requireActivity().setResult(RESULT_CANCELED);
- requireActivity().finish();
- });
- }
-
- View view = buildTabbedView(requireActivity().getLayoutInflater());
- dialogBuilder.setView(view);
-
- return dialogBuilder.create();
- }
-
- @Override
- public void onCancel(@NonNull @NotNull DialogInterface dialog) {
- super.onCancel(dialog);
- if (!requireActivity().isChangingConfigurations()) {
- requireActivity().finish();
- }
- }
-
- @Override
- public void onDismiss(@NonNull @NotNull DialogInterface dialog) {
- super.onDismiss(dialog);
- if (!requireActivity().isChangingConfigurations()) {
- requireActivity().finish();
- }
- }
-
- private void setSuccessResultWithSelectedRingtone() {
- requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
- mRingtonePickerViewModel.getSelectedRingtoneUri()));
- }
-
- /**
- * Inflates the tabbed layout view and adds the required fragments. If there's only one
- * fragment to display, then the tab area is hidden.
- * @param inflater The LayoutInflater that is used to inflate the tabbed view.
- * @return The tabbed view.
- */
- private View buildTabbedView(@NonNull LayoutInflater inflater) {
- View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
- TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
- ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
-
- ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
- addFragments(adapter);
-
- if (adapter.getItemCount() == 1) {
- // Hide the tab area since there's only one fragment to display.
- tabLayout.setVisibility(View.GONE);
- }
-
- viewPager.setAdapter(adapter);
- viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
- new TabLayoutMediator(tabLayout, viewPager,
- (tab, position) -> tab.setText(adapter.getTitle(position))).attach();
-
- return view;
- }
-
- /**
- * Adds the appropriate fragments to the adapter based on the PickerType.
- *
- * @param adapter The adapter to add the fragments to.
- */
- private void addFragments(ViewPagerAdapter adapter) {
- switch (mRingtonePickerViewModel.getPickerConfig().mPickerType) {
- case RINGTONE_PICKER:
- adapter.addFragment(getString(R.string.sound_page_title),
- new SoundPickerFragment());
- adapter.addFragment(getString(R.string.vibration_page_title),
- new VibrationPickerFragment());
- break;
- case SOUND_PICKER:
- adapter.addFragment(getString(R.string.sound_page_title),
- new SoundPickerFragment());
- break;
- case VIBRATION_PICKER:
- adapter.addFragment(getString(R.string.vibration_page_title),
- new VibrationPickerFragment());
- break;
- default:
- adapter.addFragment(getString(R.string.sound_page_title),
- new SoundPickerFragment());
- break;
- }
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
deleted file mode 100644
index 7412c19..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.lifecycle.ViewModelProvider;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select vibration or silent (no vibration).
- */
-public class VibrationPickerFragment extends BasePickerFragment {
-
- @Override
- public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- super.onViewCreated(view, savedInstanceState);
- }
-
- @Override
- protected RingtoneListHandler getRingtoneListHandler() {
- return mRingtonePickerViewModel.getVibrationListHandler();
- }
-
- @Override
- protected void addRingtoneAsync() {
- // no-op
- }
-
- @Override
- protected void addNewRingtoneItem() {
- // no-op
- }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
deleted file mode 100644
index 179068e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.viewpager2.adapter.FragmentStateAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An adapter used to populate pages inside a ViewPager.
- */
-public class ViewPagerAdapter extends FragmentStateAdapter {
-
- private final List<Fragment> mFragments = new ArrayList<>();
- private final List<String> mTitles = new ArrayList<>();
-
- public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
- super(fragmentActivity);
- }
-
- /**
- * Adds a fragment and page title to the adapter.
- * @param title the title of the page in the ViewPager.
- * @param fragment the fragment that will be inflated on this page.
- */
- public void addFragment(String title, Fragment fragment) {
- mTitles.add(title);
- mFragments.add(fragment);
- }
-
- /**
- * Returns the title of the requested page.
- * @param position the position of the page in the Viewpager.
- * @return The title of the requested page.
- */
- public String getTitle(int position) {
- return mTitles.get(position);
- }
-
- @NonNull
- @Override
- public Fragment createFragment(int position) {
- return Objects.requireNonNull(mFragments.get(position),
- "Could not find a fragment using position: " + position);
- }
-
- @Override
- public int getItemCount() {
- return mFragments.size();
- }
-}
diff --git a/packages/SoundPicker2/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
deleted file mode 100644
index d88d442..0000000
--- a/packages/SoundPicker2/tests/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "SoundPicker2Tests",
- certificate: "platform",
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
- static_libs: [
- "androidx.test.core",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "androidx.test.ext.truth",
- "mockito-target-minus-junit4",
- "guava-android-testlib",
- "SoundPicker2Lib",
- ],
- srcs: [
- "src/**/*.java",
- ],
-}
diff --git a/packages/SoundPicker2/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
deleted file mode 100644
index 295aeb1..0000000
--- a/packages/SoundPicker2/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.soundpicker.tests">
-
- <application android:debuggable="true">
- <uses-library android:name="android.test.runner" />
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.soundpicker.tests"
- android:label="Sound picker tests">
- </instrumentation>
-</manifest>
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
deleted file mode 100644
index 80e71e200..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RingtoneListHandlerTest {
-
- private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
- private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
- private static final int SILENT_RINGTONE_POSITION = 0;
- private static final int DEFAULT_RINGTONE_POSITION = 1;
- private static final int RINGTONE_POSITION = 2;
-
- @Mock
- private RingtoneManager mMockRingtoneManager;
- @Mock
- private Cursor mMockCursor;
-
- private RingtoneListHandler mRingtoneListHandler;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
-
- mRingtoneListHandler = new RingtoneListHandler();
-
- // Add silent and default options to the list.
- mRingtoneListHandler.addSilentItem();
- mRingtoneListHandler.addDefaultItem();
-
- mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
- }
-
- @Test
- public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
- assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
- }
-
- @Test
- public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
- Uri expectedUri = RINGTONE_URI;
- when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
-
- // Request 3rd item from list.
- Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
- assertThat(actualUri).isEqualTo(expectedUri);
- }
-
- @Test
- public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
- Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
- assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
- }
-
- @Test
- public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
- Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
- assertThat(actualUri).isEqualTo(DEFAULT_URI);
- }
-
- @Test
- public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
- Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
- assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
- }
-
- @Test
- public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
- mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
- Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
- assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
- mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
- assertThat(actualUri).isEqualTo(DEFAULT_URI);
-
- mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
- actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
- assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
- when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
- mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
- actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
- assertThat(actualUri).isEqualTo(RINGTONE_URI);
- }
-
- @Test
- public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
- when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
-
- int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
-
- assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
-
- }
-
- @Test
- public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
- // Clear fixed items before testing the add methods.
- mRingtoneListHandler.resetFixedItems();
-
- assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
- RingtoneListHandler.ITEM_POSITION_UNKNOWN);
- assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
- RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-
- mRingtoneListHandler.addSilentItem();
- mRingtoneListHandler.addDefaultItem();
- mRingtoneListHandler.addSilentItem();
- mRingtoneListHandler.addDefaultItem();
-
- assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
- SILENT_RINGTONE_POSITION);
- assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
- DEFAULT_RINGTONE_POSITION);
- }
-
- @Test
- public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
- assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
- SILENT_RINGTONE_POSITION);
- assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
- DEFAULT_RINGTONE_POSITION);
-
- mRingtoneListHandler.resetFixedItems();
-
- assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
- RingtoneListHandler.ITEM_POSITION_UNKNOWN);
- assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
- RingtoneListHandler.ITEM_POSITION_UNKNOWN);
- }
-
- private RingtoneListHandler.Config createRingtoneListConfig() {
- return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
- /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
- /* existingUri= */ DEFAULT_URI);
- }
-}
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
deleted file mode 100644
index cde6c76..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.testing.TestingExecutors;
-
-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 java.io.IOException;
-import java.util.concurrent.ExecutorService;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtonePickerViewModelTest {
-
- private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
- private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
- private static final int RINGTONE_TYPE_UNKNOWN = -1;
- private static final int DEFAULT_RINGTONE_POSITION = 1;
-
- @Mock
- private RingtoneManagerFactory mMockRingtoneManagerFactory;
- @Mock
- private RingtoneFactory mMockRingtoneFactory;
- @Mock
- private RingtoneManager mMockRingtoneManager;
- @Mock
- private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
- @Mock
- private Cursor mMockCursor;
-
- private RingtoneListHandler mSoundListHandler;
- private RingtoneListHandler mVibrationListHandler;
- private ExecutorService mMainThreadExecutor;
- private ListeningExecutorService mBackgroundThreadExecutor;
- private Ringtone mMockDefaultRingtone;
- private Ringtone mMockRingtone;
- private RingtonePickerViewModel mViewModel;
- private RingtoneListHandler.Config mSoundListConfig;
- private RingtoneListHandler.Config mVibrationListConfig;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mSoundListHandler = new RingtoneListHandler();
- mVibrationListHandler = new RingtoneListHandler();
- mSoundListConfig = createRingtoneListConfig();
- mVibrationListConfig = createRingtoneListConfig();
- mMockDefaultRingtone = createMockRingtone();
- mMockRingtone = createMockRingtone();
- when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
- when(mMockRingtoneFactory.create(DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
- when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
- when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
- mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
- mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- mBackgroundThreadExecutor);
-
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory, mSoundListHandler,
- mVibrationListHandler);
-
- // Add silent and default options to the sound list.
- mSoundListHandler.addSilentItem();
- mSoundListHandler.addDefaultItem();
-
- // Add silent and default options to the vibration list.
- mVibrationListHandler.addSilentItem();
- mVibrationListHandler.addDefaultItem();
-
- mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- }
-
- @After
- public void teardown() {
- if (mMainThreadExecutor != null && !mMainThreadExecutor.isShutdown()) {
- mMainThreadExecutor.shutdown();
- }
- if (mBackgroundThreadExecutor != null && !mBackgroundThreadExecutor.isShutdown()) {
- mBackgroundThreadExecutor.shutdown();
- }
- }
-
- @Test
- public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
- mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
- mVibrationListConfig);
-
- verify(mMockRingtoneManagerFactory).create();
- verify(mMockRingtoneManager, never()).setType(anyInt());
- assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
- assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
- }
-
- @Test
- public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
- mVibrationListConfig);
-
- verify(mMockRingtoneManagerFactory).create();
- verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
- assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
- assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
- }
-
- @Test
- public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
- mViewModel.init(
- createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
- /* soundListConfig= */ null, /* vibrationListConfig= */ null);
-
- verify(mMockRingtoneManagerFactory).create();
- verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
- assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
- assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
- }
-
- @Test
- public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.reinit();
-
- verify(mMockRingtoneManagerFactory, times(2)).create();
- verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
- assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
- assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
- }
-
- @Test
- public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
- mViewModel.init(
- createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
- /* soundListConfig= */ null, /* vibrationListConfig= */ null);
- mViewModel.reinit();
-
- verify(mMockRingtoneManagerFactory, times(2)).create();
- verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
- assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
- assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
- }
-
- @Test
- public void testGetStreamType_returnsTheCorrectStreamType() {
- when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
- }
-
- @Test
- public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- mViewModel.onPause(/* isChangingConfigurations= */ true);
- verify(mMockDefaultRingtone, never()).stop();
- }
-
- @Test
- public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- mViewModel.onPause(/* isChangingConfigurations= */ false);
- verify(mMockDefaultRingtone).stop();
- }
-
- @Test
- public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- Ringtone mockRingtone1 = createMockRingtone();
- Ringtone mockRingtone2 = createMockRingtone();
-
- when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
- // Fake a scenario where the activity is destroyed and recreated due to a config change.
- // This will result in a new view model getting created.
- mViewModel.onStop(/* isChangingConfigurations= */ true);
- verify(mockRingtone1, never()).stop();
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory, mSoundListHandler,
- mVibrationListHandler);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
- verify(mockRingtone1).stop();
- verify(mockRingtone2, never()).stop();
- }
-
- @Test
- public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- mViewModel.onStop(/* isChangingConfigurations= */ true);
- assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
- }
-
- @Test
- public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- mViewModel.onStop(/* isChangingConfigurations= */ true);
- assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
- }
-
- @Test
- public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
- mViewModel.onStop(/* isChangingConfigurations= */ true);
- assertNull(RingtonePickerViewModel.sPlayingRingtone);
- }
-
- @Test
- public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
-
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- mViewModel.onStop(/* isChangingConfigurations= */ false);
- verify(mMockDefaultRingtone).stop();
- }
-
- @Test
- public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
-
- assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
- }
-
- @Test
- public void testPlayRingtone_playTheCorrectRingtone() {
- mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
-
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- }
-
- @Test
- public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
- // Start playing the first ringtone
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
- // Start playing the second ringtone
- when(mMockRingtoneFactory.create(DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
- mViewModel.playRingtone();
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
-
- verify(mMockDefaultRingtone).stop();
- }
-
- @Test
- public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
- Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
- RingtoneManager.TYPE_NOTIFICATION);
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, uri);
- }
-
- @Test
- public void testDefaultItemUri_withAlarmIntent_returnDefaultAlarmUri() {
- Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_ALARM);
- assertEquals(Settings.System.DEFAULT_ALARM_ALERT_URI, uri);
- }
-
- @Test
- public void testDefaultItemUri_withRingtoneIntent_returnDefaultRingtoneUri() {
- Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_RINGTONE);
- assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
- }
-
- @Test
- public void testDefaultItemUri_withInvalidRingtoneType_returnDefaultRingtoneUri() {
- Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(-1);
- assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
- }
-
- @Test
- public void testTitle_withNotificationRingtoneType_returnRingtoneNotificationTitle() {
- int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_NOTIFICATION);
- assertEquals(com.android.internal.R.string.ringtone_picker_title_notification, title);
- }
-
- @Test
- public void testTitle_withAlarmRingtoneType_returnRingtoneAlarmTitle() {
- int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_ALARM);
- assertEquals(com.android.internal.R.string.ringtone_picker_title_alarm, title);
- }
-
- @Test
- public void testTitle_withInvalidRingtoneType_returnDefaultRingtoneTitle() {
- int title = RingtonePickerViewModel.getTitleByType(/*ringtoneType= */ -1);
- assertEquals(com.android.internal.R.string.ringtone_picker_title, title);
- }
-
- @Test
- public void testAddNewItemText_withAlarmType_returnAlarmAddItemText() {
- int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
- RingtoneManager.TYPE_ALARM);
- assertEquals(R.string.add_alarm_text, addNewItemTextResId);
- }
-
- @Test
- public void testAddNewItemText_withNotificationType_returnNotificationAddItemText() {
- int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
- RingtoneManager.TYPE_NOTIFICATION);
- assertEquals(R.string.add_notification_text, addNewItemTextResId);
- }
-
- @Test
- public void testAddNewItemText_withRingtoneType_returnRingtoneAddItemText() {
- int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
- RingtoneManager.TYPE_RINGTONE);
- assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
- }
-
- @Test
- public void testAddNewItemText_withInvalidType_returnRingtoneAddItemText() {
- int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(-1);
- assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
- }
-
- @Test
- public void testDefaultItemText_withNotificationType_returnNotificationDefaultItemText() {
- int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- RingtoneManager.TYPE_NOTIFICATION);
- assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
- }
-
- @Test
- public void testDefaultItemText_withAlarmType_returnAlarmDefaultItemText() {
- int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- RingtoneManager.TYPE_NOTIFICATION);
- assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
- }
-
- @Test
- public void testDefaultItemText_withRingtoneType_returnRingtoneDefaultItemText() {
- int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- RingtoneManager.TYPE_RINGTONE);
- assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
- }
-
- @Test
- public void testDefaultItemText_withInvalidType_returnRingtoneDefaultItemText() {
- int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(-1);
- assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
- }
-
- @Test
- public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
-
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory, mSoundListHandler,
- mVibrationListHandler);
- mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
- mockCallback, mMainThreadExecutor);
- verify(mockCallback, never()).onFailure(any());
- // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
- // task invokes the onFailure method in the callable.
- mViewModel.cancelPendingAsyncTasks();
- verify(mockCallback).onFailure(any());
- verify(mockCallback, never()).onSuccess(any());
-
- }
-
- @Test
- public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
- throws IOException {
- FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
- FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
-
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory, mSoundListHandler,
- mVibrationListHandler);
- mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
- mockCallback1, mMainThreadExecutor);
- verify(mockCallback1, never()).onFailure(any());
- // We call addRingtoneAsync again to cancel the previous task and start a new one.
- // Cancelling an async task invokes the onFailure method in the callable.
- mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
- mockCallback2, mMainThreadExecutor);
- verify(mockCallback1).onFailure(any());
- verify(mockCallback1, never()).onSuccess(any());
- verifyNoMoreInteractions(mockCallback2);
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
- throws IOException {
- Uri expectedUri = DEFAULT_URI;
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
-
- mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
- mockCallback, mMainThreadExecutor);
-
- verify(mockCallback).onSuccess(expectedUri);
- verify(mockCallback, never()).onFailure(any());
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
- IOException.class);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
- mVibrationListConfig);
-
- mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
- mockCallback, mMainThreadExecutor);
-
- verify(mockCallback).onFailure(any(IOException.class));
- verify(mockCallback, never()).onSuccess(any());
- }
-
- private Ringtone createMockRingtone() {
- Ringtone mockRingtone = mock(Ringtone.class);
- when(mockRingtone.getAudioAttributes()).thenReturn(
- audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, 0));
-
- return mockRingtone;
- }
-
- private void verifyRingtonePlayCalledAndMockPlayingState(Ringtone ringtone) {
- verify(ringtone).play();
- when(ringtone.isPlaying()).thenReturn(true);
- }
-
- private static AudioAttributes audioAttributes(int audioUsage, int flags) {
- return new AudioAttributes.Builder()
- .setUsage(audioUsage)
- .setFlags(flags)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build();
- }
-
- private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
- int audioAttributes) {
- return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
- ringtoneType, /* showOkCancelButtons= */ true,
- audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
- }
-
- private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
- return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
- ringtoneType, /* showOkCancelButtons= */ true,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
- RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
- }
-
- private RingtoneListHandler.Config createRingtoneListConfig() {
- return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
- /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
- /* existingUri= */ Uri.parse(""));
- }
-}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f877d7a..12e8f57 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -262,6 +262,9 @@
<uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />
+ <!-- Activity Manager -->
+ <uses-permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY" />
+
<!-- accessibility -->
<uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 8194055..c489795 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -45,20 +45,20 @@
import com.android.internal.policy.ScreenDecorationsUtils
import kotlin.math.roundToInt
-private const val TAG = "ActivityLaunchAnimator"
+private const val TAG = "ActivityTransitionAnimator"
/**
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
-class ActivityLaunchAnimator(
+class ActivityTransitionAnimator(
/** The animator used when animating a View into an app. */
- private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
/** The animator used when animating a Dialog into an app. */
// TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
// TIMINGS.contentBeforeFadeOutDuration.
- private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+ private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -71,7 +71,7 @@
/** The timings when animating a View into an app. */
@JvmField
val TIMINGS =
- LaunchAnimator.Timings(
+ TransitionAnimator.Timings(
totalDuration = 500L,
contentBeforeFadeOutDelay = 0L,
contentBeforeFadeOutDuration = 150L,
@@ -89,7 +89,7 @@
/** The interpolators when animating a View or a dialog into an app. */
val INTERPOLATORS =
- LaunchAnimator.Interpolators(
+ TransitionAnimator.Interpolators(
positionInterpolator = Interpolators.EMPHASIZED,
positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT,
contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
@@ -97,10 +97,11 @@
)
// TODO(b/288507023): Remove this flag.
- @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
+ @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
- private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
- private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
+ private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
+ private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
+ TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
@@ -112,13 +113,13 @@
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
/** The time we wait before timing out the remote animation after starting the intent. */
- private const val LAUNCH_TIMEOUT = 1_000L
+ private const val TRANSITION_TIMEOUT = 1_000L
/**
* The time we wait before we Log.wtf because the remote animation was neither started or
* cancelled by WM.
*/
- private const val LONG_LAUNCH_TIMEOUT = 5_000L
+ private const val LONG_TRANSITION_TIMEOUT = 5_000L
}
/**
@@ -133,20 +134,20 @@
/** Top-level listener that can be used to notify all registered [listeners]. */
private val lifecycleListener =
object : Listener {
- override fun onLaunchAnimationStart() {
- listeners.forEach { it.onLaunchAnimationStart() }
+ override fun onTransitionAnimationStart() {
+ listeners.forEach { it.onTransitionAnimationStart() }
}
- override fun onLaunchAnimationEnd() {
- listeners.forEach { it.onLaunchAnimationEnd() }
+ override fun onTransitionAnimationEnd() {
+ listeners.forEach { it.onTransitionAnimationEnd() }
}
- override fun onLaunchAnimationProgress(linearProgress: Float) {
- listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
+ override fun onTransitionAnimationProgress(linearProgress: Float) {
+ listeners.forEach { it.onTransitionAnimationProgress(linearProgress) }
}
- override fun onLaunchAnimationCancelled() {
- listeners.forEach { it.onLaunchAnimationCancelled() }
+ override fun onTransitionAnimationCancelled() {
+ listeners.forEach { it.onTransitionAnimationCancelled() }
}
}
@@ -154,7 +155,7 @@
* Start an intent and animate the opening window. The intent will be started by running
* [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
* result. [controller] is responsible from animating the view from which the intent was started
- * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
+ * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window
* opening.
*
* If [controller] is null or [animate] is false, then the intent will be started and no
@@ -187,7 +188,7 @@
val callback =
this.callback
?: throw IllegalStateException(
- "ActivityLaunchAnimator.callback must be set before using this animator"
+ "ActivityTransitionAnimator.callback must be set before using this animator"
)
val runner = createRunner(controller)
val runnerDelegate = runner.delegate!!
@@ -255,11 +256,11 @@
private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
- this.launchContainer.context.mainExecutor.execute {
+ this.transitionContainer.context.mainExecutor.execute {
callOnIntentStartedOnMainThread(willAnimate)
}
} else {
- if (DEBUG_LAUNCH_ANIMATION) {
+ if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
TAG,
"Calling controller.onIntentStarted(willAnimate=$willAnimate) " +
@@ -292,7 +293,7 @@
}
}
- /** Add a [Listener] that can listen to launch animations. */
+ /** Add a [Listener] that can listen to transition animations. */
fun addListener(listener: Listener) {
listeners.add(listener)
}
@@ -306,14 +307,14 @@
@VisibleForTesting
fun createRunner(controller: Controller): Runner {
// Make sure we use the modified timings when animating a dialog into an app.
- val launchAnimator =
+ val transitionAnimator =
if (controller.isDialogLaunch) {
dialogToAppAnimator
} else {
- launchAnimator
+ transitionAnimator
}
- return Runner(controller, callback!!, launchAnimator, lifecycleListener)
+ return Runner(controller, callback!!, transitionAnimator, lifecycleListener)
}
interface PendingIntentStarter {
@@ -339,24 +340,24 @@
}
interface Listener {
- /** Called when an activity launch animation started. */
- fun onLaunchAnimationStart() {}
+ /** Called when an activity transition animation started. */
+ fun onTransitionAnimationStart() {}
/**
- * Called when an activity launch animation is finished. This will be called if and only if
- * [onLaunchAnimationStart] was called earlier.
+ * Called when an activity transition animation is finished. This will be called if and only
+ * if [onTransitionAnimationStart] was called earlier.
*/
- fun onLaunchAnimationEnd() {}
+ fun onTransitionAnimationEnd() {}
/**
- * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
- * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
- * before the cancellation.
+ * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
+ * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
+ * called before the cancellation.
*/
- fun onLaunchAnimationCancelled() {}
+ fun onTransitionAnimationCancelled() {}
- /** Called when an activity launch animation made progress. */
- fun onLaunchAnimationProgress(linearProgress: Float) {}
+ /** Called when an activity transition animation made progress. */
+ fun onTransitionAnimationProgress(linearProgress: Float) {}
}
/**
@@ -364,7 +365,7 @@
*
* Note that all callbacks (onXXX methods) are all called on the main thread.
*/
- interface Controller : LaunchAnimator.Controller {
+ interface Controller : TransitionAnimator.Controller {
companion object {
/**
* Return a [Controller] that will animate and expand [view] into the opening window.
@@ -382,9 +383,10 @@
// issues.
if (view !is LaunchableView) {
throw IllegalArgumentException(
- "An ActivityLaunchAnimator.Controller was created from a View that does " +
- "not implement LaunchableView. This can lead to subtle bugs where the" +
- " visibility of the View we are launching from is not what we expected."
+ "An ActivityTransitionAnimator.Controller was created from a View that " +
+ "does not implement LaunchableView. This can lead to subtle bugs " +
+ "where the visibility of the View we are launching from is not what " +
+ "we expected."
)
}
@@ -410,11 +412,11 @@
get() = false
/**
- * Whether the expandable controller by this [Controller] is below the launching window that
- * is going to be animated.
+ * Whether the expandable controller by this [Controller] is below the window that is going
+ * to be animated.
*
- * This should be `false` when launching an app from the shade or status bar, given that
- * they are drawn above all apps. This is usually `true` when using this launcher in a
+ * This should be `false` when animating an app from or to the shade or status bar, given
+ * that they are drawn above all apps. This is usually `true` when using this animator in a
* normal app or a launcher, that are drawn below the animating activity/window.
*/
val isBelowAnimatingWindow: Boolean
@@ -427,14 +429,15 @@
fun onIntentStarted(willAnimate: Boolean) {}
/**
- * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
- * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
- * before the cancellation.
+ * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
+ * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
+ * called before the cancellation.
*
- * If this launch animation affected the occlusion state of the keyguard, WM will provide us
- * with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
+ * If this transition animation affected the occlusion state of the keyguard, WM will
+ * provide us with [newKeyguardOccludedState] so that we can set the occluded state
+ * appropriately.
*/
- fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+ fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
}
/**
@@ -448,24 +451,24 @@
) : Listener {
var cancelled = false
- override fun onLaunchAnimationStart() {
- delegate?.onLaunchAnimationStart()
+ override fun onTransitionAnimationStart() {
+ delegate?.onTransitionAnimationStart()
}
- override fun onLaunchAnimationProgress(linearProgress: Float) {
- delegate?.onLaunchAnimationProgress(linearProgress)
+ override fun onTransitionAnimationProgress(linearProgress: Float) {
+ delegate?.onTransitionAnimationProgress(linearProgress)
}
- override fun onLaunchAnimationEnd() {
- delegate?.onLaunchAnimationEnd()
+ override fun onTransitionAnimationEnd() {
+ delegate?.onTransitionAnimationEnd()
if (!cancelled) {
onAnimationComplete.invoke()
}
}
- override fun onLaunchAnimationCancelled() {
+ override fun onTransitionAnimationCancelled() {
cancelled = true
- delegate?.onLaunchAnimationCancelled()
+ delegate?.onTransitionAnimationCancelled()
onAnimationComplete.invoke()
}
}
@@ -474,12 +477,12 @@
inner class Runner(
controller: Controller,
callback: Callback,
- /** The animator to use to animate the window launch. */
- launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ /** The animator to use to animate the window transition. */
+ transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
/** Listener for animation lifecycle events. */
listener: Listener? = null
) : IRemoteAnimationRunner.Stub() {
- private val context = controller.launchContainer.context
+ private val context = controller.transitionContainer.context
// This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
// etc.) are possible. So we need to make sure we drop any references that might
@@ -492,7 +495,7 @@
controller,
callback,
DelegatingAnimationCompletionListener(listener, this::dispose),
- launchAnimator,
+ transitionAnimator,
disableWmTimeout
)
}
@@ -542,8 +545,8 @@
private val callback: Callback,
/** Listener for animation lifecycle events. */
private val listener: Listener? = null,
- /** The animator to use to animate the window launch. */
- private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ /** The animator to use to animate the window transition. */
+ private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -552,10 +555,10 @@
// TODO(b/301385865): Remove this flag.
disableWmTimeout: Boolean = false,
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
- private val launchContainer = controller.launchContainer
- private val context = launchContainer.context
+ private val transitionContainer = controller.transitionContainer
+ private val context = transitionContainer.context
private val transactionApplierView =
- controller.openingWindowSyncView ?: controller.launchContainer
+ controller.openingWindowSyncView ?: controller.transitionContainer
private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
private val timeoutHandler =
if (!disableWmTimeout) {
@@ -570,11 +573,11 @@
private var windowCropF = RectF()
private var timedOut = false
private var cancelled = false
- private var animation: LaunchAnimator.Animation? = null
+ private var animation: TransitionAnimator.Animation? = null
/**
- * A timeout to cancel the launch animation if the remote animation is not started or
- * cancelled within [LAUNCH_TIMEOUT] milliseconds after the intent was started.
+ * A timeout to cancel the transition animation if the remote animation is not started or
+ * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started.
*
* Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
* it will be automatically converted when posted and we wouldn't be able to remove it after
@@ -584,21 +587,22 @@
/**
* A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't
- * started or cancelled within [LONG_LAUNCH_TIMEOUT] milliseconds after the intent was
+ * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was
* started.
*/
private var onLongTimeout = Runnable {
Log.wtf(
TAG,
- "The remote animation was neither cancelled or started within $LONG_LAUNCH_TIMEOUT"
+ "The remote animation was neither cancelled or started within " +
+ "$LONG_TRANSITION_TIMEOUT"
)
}
@UiThread
internal fun postTimeouts() {
if (timeoutHandler != null) {
- timeoutHandler.postDelayed(onTimeout, LAUNCH_TIMEOUT)
- timeoutHandler.postDelayed(onLongTimeout, LONG_LAUNCH_TIMEOUT)
+ timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT)
+ timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT)
}
}
@@ -660,7 +664,7 @@
nonApps: Array<out RemoteAnimationTarget>?,
iCallback: IRemoteAnimationFinishedCallback?
) {
- if (LaunchAnimator.DEBUG) {
+ if (TransitionAnimator.DEBUG) {
Log.d(TAG, "Remote animation started")
}
@@ -669,14 +673,14 @@
Log.i(TAG, "Aborting the animation as no window is opening")
iCallback?.invoke()
- if (DEBUG_LAUNCH_ANIMATION) {
+ if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
TAG,
- "Calling controller.onLaunchAnimationCancelled() [no window opening]"
+ "Calling controller.onTransitionAnimationCancelled() [no window opening]"
)
}
- controller.onLaunchAnimationCancelled()
- listener?.onLaunchAnimationCancelled()
+ controller.onTransitionAnimationCancelled()
+ listener?.onTransitionAnimationCancelled()
return
}
@@ -687,7 +691,7 @@
val windowBounds = window.screenSpaceBounds
val endState =
- LaunchAnimator.State(
+ TransitionAnimator.State(
top = windowBounds.top,
bottom = windowBounds.bottom,
left = windowBounds.left,
@@ -699,7 +703,7 @@
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
// instead of recomputing isExpandingFullyAbove here.
val isExpandingFullyAbove =
- launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
+ transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
val endRadius =
if (isExpandingFullyAbove) {
// Most of the time, expanding fully above the root view means expanding in full
@@ -718,35 +722,37 @@
val delegate = this.controller
val controller =
object : Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- listener?.onLaunchAnimationStart()
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ listener?.onTransitionAnimationStart()
- if (DEBUG_LAUNCH_ANIMATION) {
+ if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
TAG,
- "Calling controller.onLaunchAnimationStart(isExpandingFullyAbove=" +
- "$isExpandingFullyAbove) [controller=$delegate]"
+ "Calling controller.onTransitionAnimationStart(" +
+ "isExpandingFullyAbove=$isExpandingFullyAbove) " +
+ "[controller=$delegate]"
)
}
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- listener?.onLaunchAnimationEnd()
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ listener?.onTransitionAnimationEnd()
iCallback?.invoke()
- if (DEBUG_LAUNCH_ANIMATION) {
+ if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
TAG,
- "Calling controller.onLaunchAnimationEnd(isExpandingFullyAbove=" +
- "$isExpandingFullyAbove) [controller=$delegate]"
+ "Calling controller.onTransitionAnimationEnd(" +
+ "isExpandingFullyAbove=$isExpandingFullyAbove) " +
+ "[controller=$delegate]"
)
}
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
@@ -757,13 +763,13 @@
}
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
- listener?.onLaunchAnimationProgress(linearProgress)
- delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+ listener?.onTransitionAnimationProgress(linearProgress)
+ delegate.onTransitionAnimationProgress(state, progress, linearProgress)
}
}
animation =
- launchAnimator.startAnimation(
+ transitionAnimator.startAnimation(
controller,
endState,
windowBackgroundColor,
@@ -774,7 +780,7 @@
private fun applyStateToWindow(
window: RemoteAnimationTarget,
- state: LaunchAnimator.State,
+ state: TransitionAnimator.State,
linearProgress: Float,
) {
if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
@@ -825,7 +831,7 @@
val alpha =
if (controller.isBelowAnimatingWindow) {
val windowProgress =
- LaunchAnimator.getProgress(
+ TransitionAnimator.getProgress(
TIMINGS,
linearProgress,
TIMINGS.contentAfterFadeInDelay,
@@ -857,7 +863,7 @@
private fun applyStateToNavigationBar(
navigationBar: RemoteAnimationTarget,
- state: LaunchAnimator.State,
+ state: TransitionAnimator.State,
linearProgress: Float
) {
if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
@@ -868,7 +874,7 @@
}
val fadeInProgress =
- LaunchAnimator.getProgress(
+ TransitionAnimator.getProgress(
TIMINGS,
linearProgress,
ANIMATION_DELAY_NAV_FADE_IN,
@@ -890,7 +896,7 @@
.withVisibility(true)
} else {
val fadeOutProgress =
- LaunchAnimator.getProgress(
+ TransitionAnimator.getProgress(
TIMINGS,
linearProgress,
0,
@@ -903,7 +909,7 @@
}
private fun onAnimationTimedOut() {
- // The remote animation was cancelled by WM, so we already cancelled the launch
+ // The remote animation was cancelled by WM, so we already cancelled the transition
// animation.
if (cancelled) {
return
@@ -912,18 +918,21 @@
Log.w(TAG, "Remote animation timed out")
timedOut = true
- if (DEBUG_LAUNCH_ANIMATION) {
- Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]")
+ if (DEBUG_TRANSITION_ANIMATION) {
+ Log.d(
+ TAG,
+ "Calling controller.onTransitionAnimationCancelled() [animation timed out]"
+ )
}
- controller.onLaunchAnimationCancelled()
- listener?.onLaunchAnimationCancelled()
+ controller.onTransitionAnimationCancelled()
+ listener?.onTransitionAnimationCancelled()
}
@UiThread
override fun onAnimationCancelled() {
removeTimeouts()
- // The short timeout happened, so we already cancelled the launch animation.
+ // The short timeout happened, so we already cancelled the transition animation.
if (timedOut) {
return
}
@@ -933,14 +942,15 @@
animation?.cancel()
- if (DEBUG_LAUNCH_ANIMATION) {
+ if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
TAG,
- "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]",
+ "Calling controller.onTransitionAnimationCancelled() [remote animation " +
+ "cancelled]",
)
}
- controller.onLaunchAnimationCancelled()
- listener?.onLaunchAnimationCancelled()
+ controller.onTransitionAnimationCancelled()
+ listener?.onTransitionAnimationCancelled()
}
private fun IRemoteAnimationFinishedCallback.invoke() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
index b879ba0..a53ab62 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
@@ -17,10 +17,10 @@
package com.android.systemui.animation
/**
- * A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which
+ * A base class to easily create an implementation of [ActivityTransitionAnimator.Controller] which
* delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily
* create such a delegated class.
*/
open class DelegateLaunchAnimatorController(
- protected val delegate: ActivityLaunchAnimator.Controller
-) : ActivityLaunchAnimator.Controller by delegate
+ protected val delegate: ActivityTransitionAnimator.Controller
+) : ActivityTransitionAnimator.Controller by delegate
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 168039e..ed7f31c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -58,17 +58,18 @@
private val callback: Callback,
private val interactionJankMonitor: InteractionJankMonitor,
private val featureFlags: AnimationFeatureFlags,
- private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+ private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
private val isForTesting: Boolean = false,
) {
private companion object {
- private val TIMINGS = ActivityLaunchAnimator.TIMINGS
+ private val TIMINGS = ActivityTransitionAnimator.TIMINGS
// We use the same interpolator for X and Y axis to make sure the dialog does not move out
// of the screen bounds during the animation.
private val INTERPOLATORS =
- ActivityLaunchAnimator.INTERPOLATORS.copy(
- positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
+ ActivityTransitionAnimator.INTERPOLATORS.copy(
+ positionXInterpolator =
+ ActivityTransitionAnimator.INTERPOLATORS.positionInterpolator
)
}
@@ -108,21 +109,21 @@
fun stopDrawingInOverlay()
/**
- * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * Create the [TransitionAnimator.Controller] that will be called to animate the source
* controlled by this [Controller] during the dialog launch animation.
*
* At the end of this animation, the source should *not* be visible anymore (until the
* dialog is closed and is animated back into the source).
*/
- fun createLaunchController(): LaunchAnimator.Controller
+ fun createTransitionController(): TransitionAnimator.Controller
/**
- * Create the [LaunchAnimator.Controller] that will be called to animate the source
+ * Create the [TransitionAnimator.Controller] that will be called to animate the source
* controlled by this [Controller] during the dialog exit animation.
*
* At the end of this animation, the source should be visible again.
*/
- fun createExitController(): LaunchAnimator.Controller
+ fun createExitController(): TransitionAnimator.Controller
/**
* Whether we should animate the dialog back into the source when it is dismissed. If this
@@ -270,7 +271,7 @@
val animatedDialog =
AnimatedDialog(
- launchAnimator = launchAnimator,
+ transitionAnimator = transitionAnimator,
callback = callback,
interactionJankMonitor = interactionJankMonitor,
controller = controller,
@@ -319,9 +320,9 @@
}
/**
- * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
- * dialog that contains [View]. Note that the dialog must have been shown using this animator,
- * otherwise this method will return null.
+ * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from
+ * the dialog that contains [View]. Note that the dialog must have been shown using this
+ * animator, otherwise this method will return null.
*
* The returned controller will take care of dismissing the dialog at the right time after the
* activity started, when the dialog to app animation is done (or when it is cancelled). If this
@@ -333,7 +334,7 @@
fun createActivityLaunchController(
view: View,
cujType: Int? = null,
- ): ActivityLaunchAnimator.Controller? {
+ ): ActivityTransitionAnimator.Controller? {
val animatedDialog =
openedDialogs.firstOrNull {
it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
@@ -343,7 +344,7 @@
}
/**
- * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from
+ * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from
* [dialog]. Note that the dialog must have been shown using this animator, otherwise this
* method will return null.
*
@@ -357,7 +358,7 @@
fun createActivityLaunchController(
dialog: Dialog,
cujType: Int? = null,
- ): ActivityLaunchAnimator.Controller? {
+ ): ActivityTransitionAnimator.Controller? {
val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null
return createActivityLaunchController(animatedDialog, cujType)
}
@@ -365,7 +366,7 @@
private fun createActivityLaunchController(
animatedDialog: AnimatedDialog,
cujType: Int? = null
- ): ActivityLaunchAnimator.Controller? {
+ ): ActivityTransitionAnimator.Controller? {
// At this point, we know that the intent of the caller is to dismiss the dialog to show
// an app, so we disable the exit animation into the source because we will never want to
// run it anyways.
@@ -384,12 +385,12 @@
val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null
val controller =
- ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType)
+ ActivityTransitionAnimator.Controller.fromView(dialogContentWithBackground, cujType)
?: return null
// Wrap the controller into one that will instantly dismiss the dialog when the animation is
// done or dismiss it normally (fading it out) if the animation is cancelled.
- return object : ActivityLaunchAnimator.Controller by controller {
+ return object : ActivityTransitionAnimator.Controller by controller {
override val isDialogLaunch = true
override fun onIntentStarted(willAnimate: Boolean) {
@@ -400,14 +401,14 @@
}
}
- override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
- controller.onLaunchAnimationCancelled()
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ controller.onTransitionAnimationCancelled()
enableDialogDismiss()
dialog.dismiss()
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- controller.onLaunchAnimationStart(isExpandingFullyAbove)
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ controller.onTransitionAnimationStart(isExpandingFullyAbove)
// Make sure the dialog is not dismissed during the animation.
disableDialogDismiss()
@@ -420,8 +421,8 @@
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
// Hide the dialog then dismiss it to instantly dismiss it without playing the
// animation.
@@ -492,7 +493,7 @@
data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)
private class AnimatedDialog(
- private val launchAnimator: LaunchAnimator,
+ private val transitionAnimator: TransitionAnimator,
private val callback: DialogLaunchAnimator.Callback,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -892,7 +893,7 @@
// Create 2 controllers to animate both the dialog and the source.
val startController =
if (isLaunching) {
- controller.createLaunchController()
+ controller.createTransitionController()
} else {
GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
}
@@ -902,34 +903,34 @@
} else {
controller.createExitController()
}
- startController.launchContainer = decorView
- endController.launchContainer = decorView
+ startController.transitionContainer = decorView
+ endController.transitionContainer = decorView
val endState = endController.createAnimatorState()
val controller =
- object : LaunchAnimator.Controller {
- override var launchContainer: ViewGroup
- get() = startController.launchContainer
+ object : TransitionAnimator.Controller {
+ override var transitionContainer: ViewGroup
+ get() = startController.transitionContainer
set(value) {
- startController.launchContainer = value
- endController.launchContainer = value
+ startController.transitionContainer = value
+ endController.transitionContainer = value
}
- override fun createAnimatorState(): LaunchAnimator.State {
+ override fun createAnimatorState(): TransitionAnimator.State {
return startController.createAnimatorState()
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
// During launch, onLaunchAnimationStart will be used to remove the temporary
// touch surface ghost so it is important to call this before calling
// onLaunchAnimationStart on the controller (which will create its own ghost).
onLaunchAnimationStart()
- startController.onLaunchAnimationStart(isExpandingFullyAbove)
- endController.onLaunchAnimationStart(isExpandingFullyAbove)
+ startController.onTransitionAnimationStart(isExpandingFullyAbove)
+ endController.onTransitionAnimationStart(isExpandingFullyAbove)
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
// onLaunchAnimationEnd is called by an Animator at the end of the animation,
// on a Choreographer animation tick. The following calls will move the animated
// content from the dialog overlay back to its original position, and this
@@ -943,23 +944,23 @@
// that the move of the content back to its original window will be reflected in
// the next frame right after [onLaunchAnimationEnd] is called.
dialog.context.mainExecutor.execute {
- startController.onLaunchAnimationEnd(isExpandingFullyAbove)
- endController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
onLaunchAnimationEnd()
}
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
- startController.onLaunchAnimationProgress(state, progress, linearProgress)
+ startController.onTransitionAnimationProgress(state, progress, linearProgress)
// The end view is visible only iff the starting view is not visible.
state.visible = !state.visible
- endController.onLaunchAnimationProgress(state, progress, linearProgress)
+ endController.onTransitionAnimationProgress(state, progress, linearProgress)
// If the dialog content is complex, its dimension might change during the
// launch animation. The animation end position might also change during the
@@ -973,7 +974,7 @@
}
}
- launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
+ transitionAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
}
private fun shouldAnimateDialogIntoSource(): Boolean {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index c49a487..2ba5948 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -21,14 +21,14 @@
/** A piece of UI that can be expanded into a Dialog or an Activity. */
interface Expandable {
/**
- * Create an [ActivityLaunchAnimator.Controller] that can be used to expand this [Expandable]
- * into an Activity, or return `null` if this [Expandable] should not be animated (e.g. if it is
- * currently not attached or visible).
+ * Create an [ActivityTransitionAnimator.Controller] that can be used to expand this
+ * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated
+ * (e.g. if it is currently not attached or visible).
*
* @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
* associated to the launch that will use this controller.
*/
- fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
+ fun activityLaunchController(cujType: Int? = null): ActivityTransitionAnimator.Controller?
/**
* Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
@@ -49,8 +49,8 @@
return object : Expandable {
override fun activityLaunchController(
cujType: Int?,
- ): ActivityLaunchAnimator.Controller? {
- return ActivityLaunchAnimator.Controller.fromView(view, cujType)
+ ): ActivityTransitionAnimator.Controller? {
+ return ActivityTransitionAnimator.Controller.fromView(view, cujType)
}
override fun dialogLaunchController(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 055252b..f7148d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -42,15 +42,15 @@
private const val TAG = "GhostedViewLaunchAnimatorController"
/**
- * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView]
- * of [ghostedView] as well as an expandable background view, which are drawn and animated instead
- * of the ghosted view.
+ * A base implementation of [ActivityTransitionAnimator.Controller] which creates a
+ * [ghost][GhostView] of [ghostedView] as well as an expandable background view, which are drawn and
+ * animated instead of the ghosted view.
*
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
* the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
* during this controller instantiation.
*
- * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
+ * Note: Avoid instantiating this directly and call [ActivityTransitionAnimator.Controller.fromView]
* whenever possible instead.
*/
open class GhostedViewLaunchAnimatorController
@@ -63,14 +63,14 @@
private val cujType: Int? = null,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
-) : ActivityLaunchAnimator.Controller {
+) : ActivityTransitionAnimator.Controller {
/** The container to which we will add the ghost view and expanding background. */
- override var launchContainer = ghostedView.rootView as ViewGroup
- private val launchContainerOverlay: ViewGroupOverlay
- get() = launchContainer.overlay
+ override var transitionContainer = ghostedView.rootView as ViewGroup
+ private val transitionContainerOverlay: ViewGroupOverlay
+ get() = transitionContainer.overlay
- private val launchContainerLocation = IntArray(2)
+ private val transitionContainerLocation = IntArray(2)
/** The ghost view that is drawn and animated instead of the ghosted view. */
private var ghostView: GhostView? = null
@@ -78,8 +78,8 @@
private val ghostViewMatrix = Matrix()
/**
- * The expanding background view that will be added to [launchContainer] (below [ghostView]) and
- * animate.
+ * The expanding background view that will be added to [transitionContainer] (below [ghostView])
+ * and animate.
*/
private var backgroundView: FrameLayout? = null
@@ -92,7 +92,7 @@
private var startBackgroundAlpha: Int = 0xFF
private val ghostedViewLocation = IntArray(2)
- private val ghostedViewState = LaunchAnimator.State()
+ private val ghostedViewState = TransitionAnimator.State()
/**
* The background of the [ghostedView]. This background will be used to draw the background of
@@ -175,9 +175,9 @@
return radius * ghostedView.scaleX
}
- override fun createAnimatorState(): LaunchAnimator.State {
+ override fun createAnimatorState(): TransitionAnimator.State {
val state =
- LaunchAnimator.State(
+ TransitionAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
bottomCornerRadius = getCurrentBottomCornerRadius()
)
@@ -185,7 +185,7 @@
return state
}
- fun fillGhostedViewState(state: LaunchAnimator.State) {
+ fun fillGhostedViewState(state: TransitionAnimator.State) {
// For the animation we are interested in the area that has a non transparent background,
// so we have to take the optical insets into account.
ghostedView.getLocationOnScreen(ghostedViewLocation)
@@ -200,7 +200,7 @@
insets.right
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
if (ghostedView.parent !is ViewGroup) {
// This should usually not happen, but let's make sure we don't crash if the view was
// detached right before we started the animation.
@@ -209,7 +209,7 @@
}
backgroundView =
- FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
+ FrameLayout(transitionContainer.context).also { transitionContainerOverlay.add(it) }
// We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -225,7 +225,7 @@
// Create a ghost of the view that will be moving and fading out. This allows to fade out
// the content before fading out the background.
- ghostView = GhostView.addGhost(ghostedView, launchContainer)
+ ghostView = GhostView.addGhost(ghostedView, transitionContainer)
// [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
// adds it first to a [FrameLayout] container. It then adds _that_ container to an
@@ -244,8 +244,8 @@
cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
@@ -287,15 +287,15 @@
if (ghostedView.parent is ViewGroup) {
// Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
// view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
- GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+ GhostView.calculateMatrix(ghostedView, transitionContainer, ghostViewMatrix)
}
- launchContainer.getLocationOnScreen(launchContainerLocation)
+ transitionContainer.getLocationOnScreen(transitionContainerLocation)
ghostViewMatrix.postScale(
scale,
scale,
- ghostedViewState.centerX - launchContainerLocation[0],
- ghostedViewState.centerY - launchContainerLocation[1]
+ ghostedViewState.centerX - transitionContainerLocation[0],
+ ghostedViewState.centerY - transitionContainerLocation[1]
)
ghostViewMatrix.postTranslate(
(leftChange + rightChange) / 2f,
@@ -310,10 +310,10 @@
val rightWithInsets = state.right + insets.right
val bottomWithInsets = state.bottom + insets.bottom
- backgroundView.top = topWithInsets - launchContainerLocation[1]
- backgroundView.bottom = bottomWithInsets - launchContainerLocation[1]
- backgroundView.left = leftWithInsets - launchContainerLocation[0]
- backgroundView.right = rightWithInsets - launchContainerLocation[0]
+ backgroundView.top = topWithInsets - transitionContainerLocation[1]
+ backgroundView.bottom = bottomWithInsets - transitionContainerLocation[1]
+ backgroundView.left = leftWithInsets - transitionContainerLocation[0]
+ backgroundView.right = rightWithInsets - transitionContainerLocation[0]
val backgroundDrawable = backgroundDrawable!!
backgroundDrawable.wrapped?.let {
@@ -321,7 +321,7 @@
}
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
if (ghostView == null) {
// We didn't actually run the animation.
return
@@ -332,7 +332,7 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
- backgroundView?.let { launchContainerOverlay.remove(it) }
+ backgroundView?.let { transitionContainerOverlay.remove(it) }
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index d6eba2e..5e4276c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,17 +31,18 @@
import com.android.app.animation.Interpolators.LINEAR
import kotlin.math.roundToInt
-private const val TAG = "LaunchAnimator"
+private const val TAG = "TransitionAnimator"
-/** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+/** A base class to animate a window (activity or dialog) launch to or return from a view . */
+class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
companion object {
internal const val DEBUG = false
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
/**
- * Given the [linearProgress] of a launch animation, return the linear progress of the
- * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
+ * Given the [linearProgress] of a transition animation, return the linear progress of the
+ * sub-animation starting [delay] ms after the transition animation and that lasts
+ * [duration].
*/
@JvmStatic
fun getProgress(
@@ -58,7 +59,7 @@
}
}
- private val launchContainerLocation = IntArray(2)
+ private val transitionContainerLocation = IntArray(2)
private val cornerRadii = FloatArray(8)
/**
@@ -73,7 +74,7 @@
*
* This will be used to:
* - Get the associated [Context].
- * - Compute whether we are expanding fully above the launch container.
+ * - Compute whether we are expanding fully above the transition container.
* - Get to overlay to which we initially put the window background layer, until the opening
* window is made visible (see [openingWindowSyncView]).
*
@@ -81,7 +82,7 @@
* inside a different location, for instance to ensure correct layering during the
* animation.
*/
- var launchContainer: ViewGroup
+ var transitionContainer: ViewGroup
/**
* The [View] with which the opening app window should be synchronized with once it starts
@@ -90,7 +91,7 @@
* We will also move the window background layer to this view's overlay once the opening
* window is visible.
*
- * If null, this will default to [launchContainer].
+ * If null, this will default to [transitionContainer].
*/
val openingWindowSyncView: View?
get() = null
@@ -99,7 +100,7 @@
* Return the [State] of the view that will be animated. We will animate from this state to
* the final window state.
*
- * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
+ * Note: This state will be mutated and passed to [onTransitionAnimationProgress] during the
* animation.
*/
fun createAnimatorState(): State
@@ -107,22 +108,22 @@
/**
* The animation started. This is typically used to initialize any additional resource
* needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
- * fully above the [launchContainer].
+ * fully above the [transitionContainer].
*/
- fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
+ fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {}
/** The animation made progress and the expandable view [state] should be updated. */
- fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
+ fun onTransitionAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
/**
- * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
- * called previously. This is typically used to clean up the resources initialized when the
- * animation was started.
+ * The animation ended. This will be called *if and only if* [onTransitionAnimationStart]
+ * was called previously. This is typically used to clean up the resources initialized when
+ * the animation was started.
*/
- fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
+ fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {}
}
- /** The state of an expandable view during a [LaunchAnimator] animation. */
+ /** The state of an expandable view during a [TransitionAnimator] animation. */
open class State(
/** The position of the view in screen space coordinates. */
var top: Int = 0,
@@ -198,13 +199,13 @@
)
/**
- * Start a launch animation controlled by [controller] towards [endState]. An intermediary layer
- * with [windowBackgroundColor] will fade in then (optionally) fade out above the expanding
- * view, and should be the same background color as the opening (or closing) window.
+ * Start a transition animation controlled by [controller] towards [endState]. An intermediary
+ * layer with [windowBackgroundColor] will fade in then (optionally) fade out above the
+ * expanding view, and should be the same background color as the opening (or closing) window.
*
* If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
* the second half of the animation, and will have SRC blending mode (ultimately punching a hole
- * in the [launch container][Controller.launchContainer]) iff [drawHole] is true.
+ * in the [transition container][Controller.transitionContainer]) iff [drawHole] is true.
*/
fun startAnimation(
controller: Controller,
@@ -251,13 +252,13 @@
}
}
- val launchContainer = controller.launchContainer
- val isExpandingFullyAbove = isExpandingFullyAbove(launchContainer, endState)
+ val transitionContainer = controller.transitionContainer
+ val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
// We add an extra layer with the same color as the dialog/app splash screen background
// color, which is usually the same color of the app background. We first fade in this layer
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
- // launch container and reveal the opening window.
+ // transition container and reveal the opening window.
val windowBackgroundLayer =
GradientDrawable().apply {
setColor(windowBackgroundColor)
@@ -275,9 +276,9 @@
val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
val moveBackgroundLayerWhenAppIsVisible =
openingWindowSyncView != null &&
- openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
+ openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
- val launchContainerOverlay = launchContainer.overlay
+ val transitionContainerOverlay = transitionContainer.overlay
var cancelled = false
var movedBackgroundLayer = false
@@ -287,20 +288,20 @@
if (DEBUG) {
Log.d(TAG, "Animation started")
}
- controller.onLaunchAnimationStart(isExpandingFullyAbove)
+ controller.onTransitionAnimationStart(isExpandingFullyAbove)
- // Add the drawable to the launch container overlay. Overlays always draw
+ // Add the drawable to the transition container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
- launchContainerOverlay.add(windowBackgroundLayer)
+ transitionContainerOverlay.add(windowBackgroundLayer)
}
override fun onAnimationEnd(animation: Animator) {
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
- controller.onLaunchAnimationEnd(isExpandingFullyAbove)
- launchContainerOverlay.remove(windowBackgroundLayer)
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
if (moveBackgroundLayerWhenAppIsVisible) {
openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
@@ -353,17 +354,21 @@
// in its new container.
movedBackgroundLayer = true
- launchContainerOverlay.remove(windowBackgroundLayer)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
- ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
+ ViewRootSync.synchronizeNextDraw(
+ transitionContainer,
+ openingWindowSyncView,
+ then = {}
+ )
}
val container =
if (movedBackgroundLayer) {
openingWindowSyncView!!
} else {
- controller.launchContainer
+ controller.transitionContainer
}
applyStateToWindowBackgroundLayer(
@@ -374,7 +379,7 @@
fadeOutWindowBackgroundLayer,
drawHole
)
- controller.onLaunchAnimationProgress(state, progress, linearProgress)
+ controller.onTransitionAnimationProgress(state, progress, linearProgress)
}
animator.start()
@@ -386,30 +391,30 @@
}
}
- /** Return whether we are expanding fully above the [launchContainer]. */
- internal fun isExpandingFullyAbove(launchContainer: View, endState: State): Boolean {
- launchContainer.getLocationOnScreen(launchContainerLocation)
- return endState.top <= launchContainerLocation[1] &&
- endState.bottom >= launchContainerLocation[1] + launchContainer.height &&
- endState.left <= launchContainerLocation[0] &&
- endState.right >= launchContainerLocation[0] + launchContainer.width
+ /** Return whether we are expanding fully above the [transitionContainer]. */
+ internal fun isExpandingFullyAbove(transitionContainer: View, endState: State): Boolean {
+ transitionContainer.getLocationOnScreen(transitionContainerLocation)
+ return endState.top <= transitionContainerLocation[1] &&
+ endState.bottom >= transitionContainerLocation[1] + transitionContainer.height &&
+ endState.left <= transitionContainerLocation[0] &&
+ endState.right >= transitionContainerLocation[0] + transitionContainer.width
}
private fun applyStateToWindowBackgroundLayer(
drawable: GradientDrawable,
state: State,
linearProgress: Float,
- launchContainer: View,
+ transitionContainer: View,
fadeOutWindowBackgroundLayer: Boolean,
drawHole: Boolean
) {
// Update position.
- launchContainer.getLocationOnScreen(launchContainerLocation)
+ transitionContainer.getLocationOnScreen(transitionContainerLocation)
drawable.setBounds(
- state.left - launchContainerLocation[0],
- state.top - launchContainerLocation[1],
- state.right - launchContainerLocation[0],
- state.bottom - launchContainerLocation[1]
+ state.left - transitionContainerLocation[0],
+ state.top - transitionContainerLocation[1],
+ state.right - transitionContainerLocation[0],
+ state.bottom - transitionContainerLocation[1]
)
// Update radius.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 1290f00..e2a29ab 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -68,19 +68,19 @@
}
}
- override fun createLaunchController(): LaunchAnimator.Controller {
+ override fun createTransitionController(): TransitionAnimator.Controller {
val delegate = GhostedViewLaunchAnimatorController(source)
- return object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ return object : TransitionAnimator.Controller by delegate {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
// Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
// ghost (that ghosts only the source content, and not its background) will
// be added right after this by the delegate and will be animated.
GhostView.removeGhost(source)
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
// At this point the view visibility is restored by the delegate, so we delay the
// visibility changes again and make it invisible while the dialog is shown.
@@ -94,7 +94,7 @@
}
}
- override fun createExitController(): LaunchAnimator.Controller {
+ override fun createExitController(): TransitionAnimator.Controller {
return GhostedViewLaunchAnimatorController(source)
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index ac1ef15..8eb2f2e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -75,7 +75,7 @@
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
import kotlin.math.max
import kotlin.math.min
@@ -301,7 +301,7 @@
private fun AnimatedContentInOverlay(
color: Color,
sizeInOriginalLayout: Size,
- animatorState: State<LaunchAnimator.State?>,
+ animatorState: State<TransitionAnimator.State?>,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
content: @Composable (Expandable) -> Unit,
@@ -407,7 +407,7 @@
internal fun measureAndLayoutComposeViewInOverlay(
view: View,
- state: LaunchAnimator.State,
+ state: TransitionAnimator.State,
) {
val exactWidth = state.width
val exactHeight = state.height
@@ -449,7 +449,7 @@
}
private fun ContentDrawScope.drawBackground(
- animatorState: LaunchAnimator.State,
+ animatorState: TransitionAnimator.State,
color: Color,
border: BorderStroke?,
) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 0e7694e..84e5725 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -40,11 +40,11 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
import kotlin.math.roundToInt
/** A controller that can control animated launches from an [Expandable]. */
@@ -70,7 +70,7 @@
val layoutDirection = LocalLayoutDirection.current
// The current animation state, if we are currently animating a dialog or activity.
- val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+ val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) }
// Whether a dialog controlled by this ExpandableController is currently showing.
val isDialogShowing = remember { mutableStateOf(false) }
@@ -123,7 +123,7 @@
internal val borderStroke: BorderStroke?,
internal val composeViewRoot: View,
internal val density: Density,
- internal val animatorState: MutableState<LaunchAnimator.State?>,
+ internal val animatorState: MutableState<TransitionAnimator.State?>,
internal val isDialogShowing: MutableState<Boolean>,
internal val overlay: MutableState<ViewGroupOverlay?>,
internal val currentComposeViewInOverlay: MutableState<View?>,
@@ -135,7 +135,7 @@
object : Expandable {
override fun activityLaunchController(
cujType: Int?,
- ): ActivityLaunchAnimator.Controller? {
+ ): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
@@ -153,32 +153,32 @@
}
/**
- * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
- * animation. This controller will:
+ * Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
+ * dialog animation. This controller will:
* 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
* composeViewRoot on the screen.
* 2. Update [animatorState] with the current animation state if we are animating, or null
* otherwise.
*/
- private fun launchController(): LaunchAnimator.Controller {
- return object : LaunchAnimator.Controller {
+ private fun transitionController(): TransitionAnimator.Controller {
+ return object : TransitionAnimator.Controller {
private val rootLocationOnScreen = intArrayOf(0, 0)
- override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+ override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
animatorState.value = null
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
// We copy state given that it's always the same object that is mutated by
- // ActivityLaunchAnimator.
+ // ActivityTransitionAnimator.
animatorState.value =
- LaunchAnimator.State(
+ TransitionAnimator.State(
state.top,
state.bottom,
state.left,
@@ -195,7 +195,7 @@
}
}
- override fun createAnimatorState(): LaunchAnimator.State {
+ override fun createAnimatorState(): TransitionAnimator.State {
val boundsInRoot = boundsInComposeViewRoot.value
val outline =
shape.createOutline(
@@ -236,7 +236,7 @@
}
val rootLocation = rootLocationOnScreen()
- return LaunchAnimator.State(
+ return TransitionAnimator.State(
top = rootLocation.y.roundToInt(),
bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
left = rootLocation.x.roundToInt(),
@@ -256,19 +256,20 @@
}
}
- /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
- private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
- val delegate = launchController()
- return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */
+ private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller {
+ val delegate = transitionController()
+ return object :
+ ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
cujType?.let { InteractionJankMonitor.getInstance().end(it) }
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
overlay.value = null
}
}
@@ -293,11 +294,11 @@
}
}
- override fun createLaunchController(): LaunchAnimator.Controller {
- val delegate = launchController()
- return object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ override fun createTransitionController(): TransitionAnimator.Controller {
+ val delegate = transitionController()
+ return object : TransitionAnimator.Controller by delegate {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
// Make sure we don't draw this expandable when the dialog is showing.
isDialogShowing.value = true
@@ -305,11 +306,11 @@
}
}
- override fun createExitController(): LaunchAnimator.Controller {
- val delegate = launchController()
- return object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ override fun createExitController(): TransitionAnimator.Controller {
+ val delegate = transitionController()
+ return object : TransitionAnimator.Controller by delegate {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
isDialogShowing.value = false
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt
new file mode 100644
index 0000000..64b9f2d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import com.android.compose.theme.colorAttr
+
+/** Resolves [com.android.systemui.common.shared.model.Color] into [Color] */
+@Composable
+@ReadOnlyComposable
+fun com.android.systemui.common.shared.model.Color.toColor(): Color {
+ return when (this) {
+ is com.android.systemui.common.shared.model.Color.Attribute -> colorAttr(attribute)
+ is com.android.systemui.common.shared.model.Color.Loaded -> Color(color)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index ff53ff2..378a1e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -34,6 +34,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -43,23 +44,21 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val viewModel: LockscreenSceneViewModel,
+ viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
- viewModel.upDestinationSceneKey
- .map { pageKey ->
- destinationScenes(up = pageKey, left = viewModel.leftDestinationSceneKey)
- }
+ combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair)
+ .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
initialValue =
destinationScenes(
up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey,
+ left = viewModel.leftDestinationSceneKey.value,
)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6a8da10..f387021 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -19,11 +19,6 @@
import android.content.Context
import android.view.ViewGroup
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
@@ -45,7 +40,6 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
@SysUISingleton
class NotificationSection
@@ -53,74 +47,54 @@
constructor(
@Application private val context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
- private val controller: NotificationStackScrollLayoutController,
- private val sceneContainerFlags: SceneContainerFlags,
- private val sharedNotificationContainer: SharedNotificationContainer,
- private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
- private val stackScrollLayout: NotificationStackScrollLayout,
- private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
- private val ambientState: AmbientState,
- private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
+ controller: NotificationStackScrollLayoutController,
+ sceneContainerFlags: SceneContainerFlags,
+ sharedNotificationContainer: SharedNotificationContainer,
+ sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+ stackScrollLayout: NotificationStackScrollLayout,
+ notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ ambientState: AmbientState,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
@Main private val mainDispatcher: CoroutineDispatcher,
) {
- @Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
+
+ init {
+ if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
// This scene container section moves the NSSL to the SharedNotificationContainer.
// This also requires that SharedNotificationContainer gets moved to the
// SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
// but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
// container by the NotificationStackScrollLayoutSection.
- return
- }
-
- var isBound by remember { mutableStateOf(false) }
-
- DisposableEffect(Unit) {
- val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
-
// Ensure stackScrollLayout is a child of sharedNotificationContainer.
+
if (stackScrollLayout.parent != sharedNotificationContainer) {
(stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
}
- disposableHandles.add(
- SharedNotificationContainerBinder.bind(
- sharedNotificationContainer,
- sharedNotificationContainerViewModel,
- sceneContainerFlags,
- controller,
- notificationStackSizeCalculator,
- mainDispatcher,
- )
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel,
+ sceneContainerFlags,
+ controller,
+ notificationStackSizeCalculator,
+ mainDispatcher,
)
if (sceneContainerFlags.flexiNotifsEnabled()) {
- disposableHandles.add(
- NotificationStackAppearanceViewBinder.bind(
- context,
- sharedNotificationContainer,
- notificationStackAppearanceViewModel,
- ambientState,
- controller,
- )
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
)
}
-
- isBound = true
-
- onDispose {
- disposableHandles.forEach { it.dispose() }
- disposableHandles.clear()
- isBound = false
- }
}
+ }
- if (!isBound) {
- return
- }
-
+ @Composable
+ fun SceneScope.Notifications(modifier: Modifier = Modifier) {
NotificationStack(
viewModel = viewModel,
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index d70f82f..ef6ae2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,6 +19,7 @@
import android.util.Log
import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
@@ -140,6 +141,8 @@
) {
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
+ val scrollState = rememberScrollState()
+ val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
val navBarHeight =
@@ -180,11 +183,28 @@
// if contentHeight drops below minimum visible scrim height while scrim is
// expanded, reset scrim offset.
- LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) {
+ LaunchedEffect(contentHeight, scrimOffset) {
snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
}
+ // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
+ LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
+ snapshotFlow { syntheticScroll.value }
+ .collect { delta ->
+ val minOffset = minScrimOffset()
+ if (scrimOffset.value > minOffset) {
+ val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
+ scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+ if (remainingDelta > 0f) {
+ scrollState.scrollBy(remainingDelta)
+ }
+ } else {
+ scrollState.scrollTo(delta.roundToInt())
+ }
+ }
+ }
+
Box(
modifier =
modifier
@@ -260,7 +280,7 @@
)
}
)
- .verticalScroll(rememberScrollState())
+ .verticalScroll(scrollState)
.fillMaxWidth()
.height { (contentHeight.value + navBarHeight).roundToInt() },
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index c027c49..de8f2ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -18,21 +18,21 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MovableElementScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.colorAttr
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
@@ -44,9 +44,16 @@
import com.android.systemui.scene.ui.composable.Shade
object QuickSettings {
+ private val SCENES =
+ setOf(
+ QuickSettingsSceneKey,
+ Shade,
+ )
+
object Elements {
// TODO RENAME
- val Content = ElementKey("QuickSettingsContent")
+ val Content =
+ ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
val FooterActions = ElementKey("QuickSettingsFooterActions")
}
@@ -86,14 +93,22 @@
*/
@Composable
fun SceneScope.QuickSettings(
- modifier: Modifier = Modifier,
qsSceneAdapter: QSSceneAdapter,
+ heightProvider: () -> Int,
+ modifier: Modifier = Modifier,
) {
val contentState = stateForQuickSettingsContent()
MovableElement(
key = QuickSettings.Elements.Content,
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp)
+ modifier =
+ modifier.fillMaxWidth().layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ // Use the height of the correct view based on the scene it is being composed in
+ val height = heightProvider()
+
+ layout(placeable.width, height) { placeable.placeRelative(0, 0) }
+ }
) {
content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) }
}
@@ -118,15 +133,7 @@
qsView?.let { view ->
Box(
modifier =
- modifier
- .fillMaxWidth()
- .then(
- if (isCustomizing) {
- Modifier.fillMaxHeight()
- } else {
- Modifier.wrapContentHeight()
- }
- )
+ modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() }
) {
AndroidView(
modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 969dec3..1cbc992 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -213,8 +213,9 @@
Spacer(modifier = Modifier.height(16.dp))
// This view has its own horizontal padding
QuickSettings(
- modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
viewModel.qsSceneAdapter,
+ { viewModel.qsSceneAdapter.qsHeight },
+ modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 9f9e1f5..da1b417 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -40,6 +40,7 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
@@ -168,7 +169,7 @@
private fun toTransitionModels(
userAction: UserAction,
sceneModel: SceneModel,
-): Pair<SceneTransitionUserAction, SceneTransitionSceneKey> {
+): Pair<SceneTransitionUserAction, UserActionResult> {
return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 677df7e..cac35cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -189,8 +189,8 @@
)
)
QuickSettings(
- modifier = Modifier.height(130.dp),
viewModel.qsSceneAdapter,
+ { viewModel.qsSceneAdapter.qqsHeight },
)
if (viewModel.isMediaVisible()) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 2596d4a..9770399 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@
class SceneKey(
debugName: String,
identity: Any = Object(),
-) : Key(debugName, identity), UserActionResult {
+) : Key(debugName, identity) {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
// access internal members.
@@ -53,11 +53,6 @@
/** The unique [ElementKey] identifying this scene's root element. */
val rootElementKey = ElementKey(debugName, identity)
- // Implementation of [UserActionResult].
- override val toScene: SceneKey = this
- override val transitionKey: TransitionKey? = null
- override val distance: UserActionDistance? = null
-
override fun toString(): String {
return "SceneKey(debugName=$debugName)"
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b3d2bc9..c8fbad4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -46,7 +46,7 @@
val draggable: DraggableHandler = SceneDraggableHandler(this)
private var _swipeTransition: SwipeTransition? = null
- internal var swipeTransition: SwipeTransition
+ private var swipeTransition: SwipeTransition
get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
set(value) {
_swipeTransition = value
@@ -92,10 +92,6 @@
/** The [Swipes] associated to the current gesture. */
private var swipes: Swipes? = null
- /** The [UserActionResult] associated to up and down swipes. */
- private var upOrLeftResult: UserActionResult? = null
- private var downOrRightResult: UserActionResult? = null
-
/**
* Whether we should immediately intercept a gesture.
*
@@ -128,7 +124,7 @@
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
swipeTransition.cancelOffsetAnimation()
- updateSwipesResults(swipeTransition._fromScene)
+ swipes!!.updateSwipesResults(swipeTransition._fromScene)
return
}
@@ -144,16 +140,24 @@
}
val fromScene = layoutImpl.scene(transitionState.currentScene)
- updateSwipes(fromScene, startedPosition, pointersDown)
+ val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
+ swipes = newSwipes
+ val result = newSwipes.findUserActionResult(fromScene, overSlop, true)
- val result =
- findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true)
- ?: return
- updateTransition(SwipeTransition(fromScene, result), force = true)
- }
+ // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
+ // defined.
+ if (result == null) return
- private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
- this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+ val newSwipeTransition =
+ SwipeTransition(
+ fromScene = fromScene,
+ result = result,
+ swipes = newSwipes,
+ layoutImpl = layoutImpl,
+ orientation = orientation
+ )
+
+ updateTransition(newSwipeTransition, force = true)
}
private fun computeSwipes(
@@ -210,13 +214,6 @@
}
}
- private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
- val targetSize = this.targetSize
- return with(distance ?: DefaultSwipeDistance) {
- layoutImpl.density.absoluteDistance(targetSize, orientation)
- }
- }
-
internal fun onDrag(delta: Float) {
if (delta == 0f || !isDrivingTransition) return
swipeTransition.dragOffset += delta
@@ -226,15 +223,17 @@
val isNewFromScene = fromScene.key != swipeTransition.fromScene
val result =
- findUserActionResult(
- fromScene,
- swipeTransition.dragOffset,
- updateSwipesResults = isNewFromScene,
+ swipes!!.findUserActionResult(
+ fromScene = fromScene,
+ directionOffset = swipeTransition.dragOffset,
+ updateSwipesResults = isNewFromScene
)
- ?: run {
- onDragStopped(delta, true)
- return
- }
+
+ if (result == null) {
+ onDragStopped(velocity = delta, canChangeScene = true)
+ return
+ }
+
swipeTransition.dragOffset += acceleratedOffset
if (
@@ -242,25 +241,20 @@
result.toScene != swipeTransition.toScene ||
result.transitionKey != swipeTransition.key
) {
- updateTransition(
- SwipeTransition(fromScene, result).apply {
- this.dragOffset = swipeTransition.dragOffset
- }
- )
+ val newSwipeTransition =
+ SwipeTransition(
+ fromScene = fromScene,
+ result = result,
+ swipes = swipes!!,
+ layoutImpl = layoutImpl,
+ orientation = orientation
+ )
+ .apply { dragOffset = swipeTransition.dragOffset }
+
+ updateTransition(newSwipeTransition)
}
}
- private fun updateSwipesResults(fromScene: Scene) {
- val (upOrLeftResult, downOrRightResult) =
- computeSwipesResults(
- fromScene,
- this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
- )
-
- this.upOrLeftResult = upOrLeftResult
- this.downOrRightResult = downOrRightResult
- }
-
private fun computeSwipesResults(
fromScene: Scene,
swipes: Swipes
@@ -295,74 +289,20 @@
// If the swipe was not committed, don't do anything.
if (swipeTransition._currentScene != toScene) {
- return Pair(fromScene, 0f)
+ return fromScene to 0f
}
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
- Pair(toScene, absoluteDistance)
- } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
- Pair(toScene, -absoluteDistance)
+ return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
+ toScene to absoluteDistance
+ } else if (
+ offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
+ ) {
+ toScene to -absoluteDistance
} else {
- Pair(fromScene, 0f)
- }
- }
-
- /**
- * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
- *
- * @param fromScene the scene from which we look for the target
- * @param directionOffset signed float that indicates the direction. Positive is down or right
- * negative is up or left.
- * @param updateSwipesResults whether the target scenes should be updated to the current values
- * held in the Scenes map. Usually we don't want to update them while doing a drag, because
- * this could change the target scene (jump cutting) to a different scene, when some system
- * state changed the targets the background. However, an update is needed any time we
- * calculate the targets for a new fromScene.
- * @return null when there are no targets in either direction. If one direction is null and you
- * drag into the null direction this function will return the opposite direction, assuming
- * that the users intention is to start the drag into the other direction eventually. If
- * [directionOffset] is 0f and both direction are available, it will default to
- * [upOrLeftResult].
- */
- private fun findUserActionResult(
- fromScene: Scene,
- directionOffset: Float,
- updateSwipesResults: Boolean,
- ): UserActionResult? {
- if (updateSwipesResults) updateSwipesResults(fromScene)
-
- return when {
- upOrLeftResult == null && downOrRightResult == null -> null
- (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
- upOrLeftResult
- else -> downOrRightResult
- }
- }
-
- /**
- * A strict version of [findUserActionResult] that will return null when there is no Scene in
- * [directionOffset] direction
- */
- private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
- return when {
- directionOffset > 0f -> upOrLeftResult
- directionOffset < 0f -> downOrRightResult
- else -> null
- }
- }
-
- private fun computeAbsoluteDistance(
- fromScene: Scene,
- result: UserActionResult,
- ): Float {
- return if (result == upOrLeftResult) {
- -fromScene.getAbsoluteDistance(result.distance)
- } else {
- check(result == downOrRightResult)
- fromScene.getAbsoluteDistance(result.distance)
+ fromScene to 0f
}
}
@@ -430,19 +370,24 @@
if (startFromIdlePosition) {
// If there is a target scene, we start the overscroll animation.
- val result =
- findUserActionResultStrict(velocity)
- ?: run {
- // We will not animate
- layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
- return
- }
+ val result = swipes!!.findUserActionResultStrict(velocity)
+ if (result == null) {
+ // We will not animate
+ layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+ return
+ }
- updateTransition(
- SwipeTransition(fromScene, result).apply {
- _currentScene = swipeTransition._currentScene
- }
- )
+ val newSwipeTransition =
+ SwipeTransition(
+ fromScene = fromScene,
+ result = result,
+ swipes = swipes!!,
+ layoutImpl = layoutImpl,
+ orientation = orientation
+ )
+ .apply { _currentScene = swipeTransition._currentScene }
+
+ updateTransition(newSwipeTransition)
animateTo(targetScene = fromScene, targetOffset = 0f)
} else {
// We were between two scenes: animate to the initial scene.
@@ -486,134 +431,220 @@
}
}
- private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
- return SwipeTransition(
- result.transitionKey,
- fromScene,
- layoutImpl.scene(result.toScene),
- computeAbsoluteDistance(fromScene, result),
- )
- }
-
- internal class SwipeTransition(
- val key: TransitionKey?,
- val _fromScene: Scene,
- val _toScene: Scene,
- /**
- * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
- * above or to the left of [toScene].
- */
- val distance: Float,
- ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
- var _currentScene by mutableStateOf(_fromScene)
- override val currentScene: SceneKey
- get() = _currentScene.key
-
- override val progress: Float
- get() {
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
- return offset / distance
- }
-
- override val isInitiatedByUserInput = true
-
- /** The current offset caused by the drag gesture. */
- var dragOffset by mutableFloatStateOf(0f)
-
- /**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by
- * gesture.
- */
- var isAnimatingOffset by mutableStateOf(false)
-
- // If we are not animating offset, it means the offset is being driven by the user's finger.
- override val isUserInputOngoing: Boolean
- get() = !isAnimatingOffset
-
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
-
- /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
- lateinit var swipeSpec: SpringSpec<Float>
-
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- private fun startOffsetAnimation(job: () -> Job) {
- cancelOffsetAnimation()
- offsetAnimationJob = job()
- }
-
- /** Cancel any ongoing offset animation. */
- // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
- // the same time.
- fun cancelOffsetAnimation() {
- offsetAnimationJob?.cancel()
- finishOffsetAnimation()
- }
-
- fun finishOffsetAnimation() {
- if (isAnimatingOffset) {
- isAnimatingOffset = false
- dragOffset = offsetAnimatable.value
- }
- }
-
- fun animateOffset(
- // TODO(b/317063114) The CoroutineScope should be removed.
- coroutineScope: CoroutineScope,
- initialVelocity: Float,
- targetOffset: Float,
- onAnimationCompleted: () -> Unit,
- ) {
- startOffsetAnimation {
- coroutineScope.launch {
- animateOffset(targetOffset, initialVelocity)
- onAnimationCompleted()
- }
- }
- }
-
- private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
- if (!isAnimatingOffset) {
- offsetAnimatable.snapTo(dragOffset)
- }
- isAnimatingOffset = true
-
- offsetAnimatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- )
-
- finishOffsetAnimation()
- }
- }
-
companion object {
private const val TAG = "SceneGestureHandler"
}
+}
- private object DefaultSwipeDistance : UserActionDistance {
- override fun Density.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return when (orientation) {
- Orientation.Horizontal -> fromSceneSize.width
- Orientation.Vertical -> fromSceneSize.height
- }.toFloat()
+private fun SwipeTransition(
+ fromScene: Scene,
+ result: UserActionResult,
+ swipes: Swipes,
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+): SwipeTransition {
+ val upOrLeftResult = swipes.upOrLeftResult
+ val downOrRightResult = swipes.downOrRightResult
+ val userActionDistance = result.distance ?: DefaultSwipeDistance
+ val absoluteDistance =
+ with(userActionDistance) {
+ layoutImpl.density.absoluteDistance(fromScene.targetSize, orientation)
+ }
+
+ return SwipeTransition(
+ key = result.transitionKey,
+ _fromScene = fromScene,
+ _toScene = layoutImpl.scene(result.toScene),
+ distance =
+ when (result) {
+ upOrLeftResult -> -absoluteDistance
+ downOrRightResult -> absoluteDistance
+ else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
+ },
+ )
+}
+
+private class SwipeTransition(
+ val key: TransitionKey?,
+ val _fromScene: Scene,
+ val _toScene: Scene,
+ /**
+ * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
+ * or to the left of [toScene]
+ */
+ val distance: Float,
+) : TransitionState.Transition(_fromScene.key, _toScene.key) {
+ var _currentScene by mutableStateOf(_fromScene)
+ override val currentScene: SceneKey
+ get() = _currentScene.key
+
+ override val progress: Float
+ get() {
+ val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ return offset / distance
+ }
+
+ override val isInitiatedByUserInput = true
+
+ /** The current offset caused by the drag gesture. */
+ var dragOffset by mutableFloatStateOf(0f)
+
+ /**
+ * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+ */
+ var isAnimatingOffset by mutableStateOf(false)
+
+ // If we are not animating offset, it means the offset is being driven by the user's finger.
+ override val isUserInputOngoing: Boolean
+ get() = !isAnimatingOffset
+
+ /** The animatable used to animate the offset once the user lifted its finger. */
+ val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+ /** Job to check that there is at most one offset animation in progress. */
+ private var offsetAnimationJob: Job? = null
+
+ /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
+ lateinit var swipeSpec: SpringSpec<Float>
+
+ /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+ private fun startOffsetAnimation(job: () -> Job) {
+ cancelOffsetAnimation()
+ offsetAnimationJob = job()
+ }
+
+ /** Cancel any ongoing offset animation. */
+ // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
+ // the same time.
+ fun cancelOffsetAnimation() {
+ offsetAnimationJob?.cancel()
+ finishOffsetAnimation()
+ }
+
+ fun finishOffsetAnimation() {
+ if (isAnimatingOffset) {
+ isAnimatingOffset = false
+ dragOffset = offsetAnimatable.value
}
}
- /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
- private class Swipes(
- val upOrLeft: Swipe?,
- val downOrRight: Swipe?,
- val upOrLeftNoSource: Swipe?,
- val downOrRightNoSource: Swipe?,
- )
+ fun animateOffset(
+ // TODO(b/317063114) The CoroutineScope should be removed.
+ coroutineScope: CoroutineScope,
+ initialVelocity: Float,
+ targetOffset: Float,
+ onAnimationCompleted: () -> Unit,
+ ) {
+ startOffsetAnimation {
+ coroutineScope.launch {
+ animateOffset(targetOffset, initialVelocity)
+ onAnimationCompleted()
+ }
+ }
+ }
+
+ private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
+ if (!isAnimatingOffset) {
+ offsetAnimatable.snapTo(dragOffset)
+ }
+ isAnimatingOffset = true
+
+ offsetAnimatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ )
+
+ finishOffsetAnimation()
+ }
+}
+
+private object DefaultSwipeDistance : UserActionDistance {
+ override fun Density.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> fromSceneSize.width
+ Orientation.Vertical -> fromSceneSize.height
+ }.toFloat()
+ }
+}
+
+/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+private class Swipes(
+ val upOrLeft: Swipe?,
+ val downOrRight: Swipe?,
+ val upOrLeftNoSource: Swipe?,
+ val downOrRightNoSource: Swipe?,
+) {
+ /** The [UserActionResult] associated to up and down swipes. */
+ var upOrLeftResult: UserActionResult? = null
+ var downOrRightResult: UserActionResult? = null
+
+ fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
+ val userActions = fromScene.userActions
+ fun result(swipe: Swipe?): UserActionResult? {
+ return userActions[swipe ?: return null]
+ }
+
+ val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
+ val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+ return upOrLeftResult to downOrRightResult
+ }
+
+ fun updateSwipesResults(fromScene: Scene) {
+ val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene)
+
+ this.upOrLeftResult = upOrLeftResult
+ this.downOrRightResult = downOrRightResult
+ }
+
+ /**
+ * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
+ *
+ * @param fromScene the scene from which we look for the target
+ * @param directionOffset signed float that indicates the direction. Positive is down or right
+ * negative is up or left.
+ * @param updateSwipesResults whether the target scenes should be updated to the current values
+ * held in the Scenes map. Usually we don't want to update them while doing a drag, because
+ * this could change the target scene (jump cutting) to a different scene, when some system
+ * state changed the targets the background. However, an update is needed any time we
+ * calculate the targets for a new fromScene.
+ * @return null when there are no targets in either direction. If one direction is null and you
+ * drag into the null direction this function will return the opposite direction, assuming
+ * that the users intention is to start the drag into the other direction eventually. If
+ * [directionOffset] is 0f and both direction are available, it will default to
+ * [upOrLeftResult].
+ */
+ fun findUserActionResult(
+ fromScene: Scene,
+ directionOffset: Float,
+ updateSwipesResults: Boolean,
+ ): UserActionResult? {
+ if (updateSwipesResults) {
+ updateSwipesResults(fromScene)
+ }
+
+ return when {
+ upOrLeftResult == null && downOrRightResult == null -> null
+ (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+ upOrLeftResult
+ else -> downOrRightResult
+ }
+ }
+
+ /**
+ * A strict version of [findUserActionResult] that will return null when there is no Scene in
+ * [directionOffset] direction
+ */
+ fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
+ return when {
+ directionOffset > 0f -> upOrLeftResult
+ directionOffset < 0f -> downOrRightResult
+ else -> null
+ }
+ }
}
private class SceneDraggableHandler(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index d904c8b..e1f8a09 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -332,7 +332,11 @@
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
/** An action performed by the user. */
-sealed interface UserAction
+sealed interface UserAction {
+ infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
+ return this to UserActionResult(toScene = scene)
+ }
+}
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
data object Back : UserAction
@@ -385,65 +389,26 @@
): SwipeSource?
}
-/**
- * The result of performing a [UserAction].
- *
- * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
- * when defining your [UserActionResult]s.
- *
- * ```
- * SceneTransitionLayout(...) {
- * scene(
- * Scenes.Foo,
- * userActions =
- * mapOf(
- * Swipe.Right to Scene.Bar,
- * Swipe.Down to Scene.Doe,
- * )
- * )
- * ) { ... }
- * }
- * ```
- */
-interface UserActionResult {
+/** The result of performing a [UserAction]. */
+class UserActionResult(
/** The scene we should be transitioning to during the [UserAction]. */
- val toScene: SceneKey
-
- /** The key of the transition that should be used. */
- val transitionKey: TransitionKey?
+ val toScene: SceneKey,
/**
* The distance the action takes to animate from 0% to 100%.
*
* If `null`, a default distance will be used that depends on the [UserAction] performed.
*/
- val distance: UserActionDistance?
-}
+ val distance: UserActionDistance? = null,
-/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
-fun UserActionResult(
- toScene: SceneKey,
- distance: UserActionDistance? = null,
- transitionKey: TransitionKey? = null,
-): UserActionResult {
- return object : UserActionResult {
- override val toScene: SceneKey = toScene
- override val transitionKey: TransitionKey? = transitionKey
- override val distance: UserActionDistance? = distance
- }
-}
-
-/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
-fun UserActionResult(
- toScene: SceneKey,
- distance: Dp,
- transitionKey: TransitionKey? = null,
-): UserActionResult {
- return UserActionResult(
- toScene = toScene,
- distance = FixedDistance(distance),
- transitionKey = transitionKey,
- )
+ /** The key of the transition that should be used. */
+ val transitionKey: TransitionKey? = null,
+) {
+ constructor(
+ toScene: SceneKey,
+ distance: Dp,
+ transitionKey: TransitionKey? = null,
+ ) : this(toScene, FixedDistance(distance), transitionKey)
}
interface UserActionDistance {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 2dc94a4..c91d298 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -54,12 +54,8 @@
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
- val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
- mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
-
- val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
- mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
-
+ val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+ val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
@@ -131,6 +127,9 @@
val progress: Float
get() = (transitionState as Transition).progress
+ val isUserInputOngoing: Boolean
+ get() = (transitionState as Transition).isUserInputOngoing
+
fun advanceUntilIdle() {
testScope.testScheduler.advanceUntilIdle()
}
@@ -507,7 +506,7 @@
onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = SceneC
+ mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -524,7 +523,7 @@
onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = SceneC
+ mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
onDelta(pixels = up(fractionOfScreen = 0.1f))
onDragStopped(velocity = down(fractionOfScreen = 0.1f))
@@ -542,12 +541,11 @@
onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
+ assertThat(isUserInputOngoing).isFalse()
// Start a new gesture while the offset is animating
onDragStartedImmediately()
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
+ assertThat(isUserInputOngoing).isTrue()
}
@Test
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 001e3a5..54c7a08 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -49,7 +49,7 @@
* existing lockscreen clock.
*/
class DefaultClockController(
- ctx: Context,
+ private val ctx: Context,
private val layoutInflater: LayoutInflater,
private val resources: Resources,
private val settings: ClockSettings?,
@@ -121,7 +121,11 @@
protected var targetRegion: Rect? = null
override val config = ClockFaceConfig()
- override val layout = DefaultClockFaceLayout(view)
+ override val layout =
+ DefaultClockFaceLayout(view).apply {
+ views[0].id =
+ resources.getIdentifier("lockscreen_clock_view", "id", ctx.packageName)
+ }
override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
internal set
@@ -188,7 +192,11 @@
seedColor: Int?,
messageBuffer: MessageBuffer?,
) : DefaultClockFaceController(view, seedColor, messageBuffer) {
- override val layout = DefaultClockFaceLayout(view)
+ override val layout =
+ DefaultClockFaceLayout(view).apply {
+ views[0].id =
+ resources.getIdentifier("lockscreen_clock_view_large", "id", ctx.packageName)
+ }
override val config =
ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index c4bcb53..f86342c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -31,6 +31,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -102,13 +103,15 @@
.thenReturn(mOkButton);
when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor =
+ new KeyguardKeyboardInteractor(new FakeKeyboardRepository());
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mEmergencyButtonController, mFalsingCollector, featureFlags,
- mSelectedUserInteractor, new FakeKeyboardRepository()) {
+ mSelectedUserInteractor, keyguardKeyboardInteractor) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index f7743e2..259f349 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -37,7 +37,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
@@ -106,7 +106,7 @@
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy
- @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@@ -167,7 +167,7 @@
reason,
controllerCallback,
onTouch,
- activityLaunchAnimator,
+ mActivityTransitionAnimator,
primaryBouncerInteractor,
alternateBouncerInteractor,
isDebuggable,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 90c3c14..529403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -75,7 +75,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.biometrics.udfps.InteractionEvent;
@@ -203,7 +203,7 @@
@Mock
private SystemUIDialogManager mSystemUIDialogManager;
@Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private ActivityTransitionAnimator mActivityTransitionAnimator;
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
@@ -331,7 +331,7 @@
mUnlockedScreenOffAnimationController,
mSystemUIDialogManager,
mLatencyTracker,
- mActivityLaunchAnimator,
+ mActivityTransitionAnimator,
mBiometricExecutor,
mPrimaryBouncerInteractor,
mShadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 7d9c2f9..324534f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -26,7 +26,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -70,7 +70,7 @@
protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
protected @Mock SystemUIDialogManager mDialogManager;
protected @Mock UdfpsController mUdfpsController;
- protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ protected @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected @Mock ShadeInteractor mShadeInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -148,7 +148,7 @@
mUnlockedScreenOffAnimationController,
mDialogManager,
mUdfpsController,
- mActivityLaunchAnimator,
+ mActivityTransitionAnimator,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index bd9ca30..b4e2eab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,26 +16,18 @@
package com.android.systemui.communal.data.repository
-import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
@@ -48,37 +40,20 @@
class CommunalRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: CommunalRepositoryImpl
- private lateinit var secureSettings: FakeSettings
- private lateinit var userRepository: FakeUserRepository
-
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneContainerRepository = kosmos.sceneContainerRepository
@Before
fun setUp() {
- secureSettings = FakeSettings()
- userRepository = kosmos.fakeUserRepository
-
- val listOfUserInfo = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(listOfUserInfo)
-
- kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
- mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
underTest = createRepositoryImpl(false)
}
private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
return CommunalRepositoryImpl(
testScope.backgroundScope,
- testScope.backgroundScope,
- kosmos.testDispatcher,
- kosmos.fakeFeatureFlagsClassic,
kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
sceneContainerRepository,
- kosmos.fakeUserRepository,
- secureSettings,
)
}
@@ -159,29 +134,4 @@
assertThat(transitionState)
.isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
}
-
- @Test
- fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
- testScope.runTest {
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
-
- val communalEnabled by collectLastValue(underTest.communalEnabledState)
- assertThat(communalEnabled).isFalse()
- }
-
- @Test
- fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
- testScope.runTest {
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
-
- val communalEnabled by collectLastValue(underTest.communalEnabledState)
- assertThat(communalEnabled).isTrue()
- }
-
- companion object {
- private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
- private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..0aca16d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+import android.app.admin.devicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var underTest: CommunalSettingsRepository
+
+ @Before
+ fun setUp() {
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
+ setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
+ underTest = kosmos.communalSettingsRepository
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun secondaryUserIsInvalid() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
+
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun classicFlagIsDisabled() =
+ testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
+ }
+
+ @DisableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun communalHubFlagIsDisabled() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun hubIsDisabledByUser() =
+ testScope.runTest {
+ kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+ val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
+
+ kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
+ assertThat(enabledState?.enabled).isFalse()
+
+ kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
+ assertThat(enabledState?.enabled).isTrue()
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun hubIsDisabledByDevicePolicy() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledState?.enabled).isTrue()
+
+ setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun hubIsDisabledByUserAndDevicePolicy() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledState?.enabled).isTrue()
+
+ kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+ setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
+
+ assertThat(enabledState?.enabled).isFalse()
+ assertThat(enabledState)
+ .containsExactly(
+ DisabledReason.DISABLED_REASON_DEVICE_POLICY,
+ DisabledReason.DISABLED_REASON_USER_SETTING,
+ )
+ }
+
+ private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
+ whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
+ .thenReturn(disabledFlags)
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ )
+ }
+
+ private companion object {
+ val PRIMARY_USER =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index 6a3fc2a..824733b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -59,7 +60,7 @@
widgetRepository = kosmos.fakeCommunalWidgetRepository
keyguardRepository = kosmos.fakeKeyguardRepository
- communalRepository.setIsCommunalEnabled(false)
+ mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
underTest = kosmos.communalInteractor
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index c5485c5..3ac19e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
@@ -41,6 +42,8 @@
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
@@ -109,12 +112,19 @@
whenever(secondaryUser.isMain).thenReturn(false)
userRepository.setUserInfos(listOf(mainUser, secondaryUser))
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+
underTest = kosmos.communalInteractor
}
@Test
fun communalEnabled_true() =
- testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(mainUser)
+ runCurrent()
+ assertThat(underTest.isCommunalEnabled).isTrue()
+ }
@Test
fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
@@ -125,7 +135,6 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setKeyguardShowing(true)
- communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isTrue()
}
@@ -139,7 +148,6 @@
keyguardRepository.setIsEncryptedOrLockdown(true)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setKeyguardShowing(true)
- communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isFalse()
}
@@ -153,7 +161,6 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(secondaryUser)
keyguardRepository.setKeyguardShowing(true)
- communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isFalse()
}
@@ -167,7 +174,6 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setDreaming(true)
- communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isTrue()
}
@@ -175,13 +181,14 @@
@Test
fun isCommunalAvailable_communalDisabled_false() =
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
+
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setKeyguardShowing(true)
- communalRepository.setCommunalEnabledState(false)
assertThat(isAvailable).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 6c87e0f..ceb7fac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -22,12 +22,15 @@
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
@@ -35,11 +38,13 @@
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -62,6 +67,8 @@
userRepository = kosmos.fakeUserRepository
userRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
underTest = kosmos.communalTutorialInteractor
}
@@ -127,6 +134,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
communalRepository.setIsCommunalHubShowing(true)
@@ -139,6 +147,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
communalRepository.setIsCommunalHubShowing(true)
@@ -151,6 +160,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
communalRepository.setIsCommunalHubShowing(true)
@@ -163,6 +173,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
communalRepository.setIsCommunalHubShowing(false)
@@ -175,6 +186,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalRepository.setIsCommunalHubShowing(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
@@ -188,6 +200,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalRepository.setIsCommunalHubShowing(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
@@ -198,14 +211,11 @@
private suspend fun setCommunalAvailable(available: Boolean) {
if (available) {
- communalRepository.setIsCommunalEnabled(true)
- communalRepository.setCommunalEnabledState(true)
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
keyguardRepository.setKeyguardShowing(true)
} else {
- communalRepository.setIsCommunalEnabled(false)
- communalRepository.setCommunalEnabledState(false)
+ keyguardRepository.setIsEncryptedOrLockdown(true)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 73d3091..f70b6a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -22,12 +22,12 @@
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -37,6 +37,8 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
@@ -92,7 +94,8 @@
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
- kosmos.fakeCommunalRepository.setCommunalEnabledState(true)
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
underTest =
CommunalViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 032d76f..8488843 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -19,12 +19,15 @@
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -33,6 +36,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -62,6 +66,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
appWidgetIdToRemove = MutableSharedFlow()
whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
@@ -169,7 +175,8 @@
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
fakeKeyguardRepository.setKeyguardShowing(true)
- fakeCommunalRepository.setCommunalEnabledState(available)
+ val settingsValue = if (available) 1 else 0
+ fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id)
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
index ea766f8..805b4a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -75,7 +74,7 @@
fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin {
// WHEN the plugin is restarted
plugin.stop()
- plugin.start()
+ plugin.startInScope(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// THEN the tracking begins again
assertThat(plugin.isTracking).isTrue()
@@ -131,22 +130,21 @@
private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
- createPlugin(this, UnconfinedTestDispatcher(testScheduler))
- // GIVEN that the plugin is started
- plugin.start()
+ val pluginScope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+ createPlugin()
+ // GIVEN that the plugin is started in a test scope
+ plugin.startInScope(pluginScope)
// THEN run the test
test()
}
}
- private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) {
+ private fun createPlugin() {
plugin =
SeekableSliderHapticPlugin(
vibratorHelper,
kosmos.fakeSystemClock,
- dispatcher,
- scope,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index a613ad8..0768340 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -23,14 +23,14 @@
import android.service.quickaccesswallet.WalletCard
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -76,26 +76,28 @@
}
@Test
- fun affordance_keyguardShowing_hasWalletCard_visibleModel() = testScope.runTest {
- setUpState()
+ fun affordance_keyguardShowing_hasWalletCard_visibleModel() =
+ testScope.runTest {
+ setUpState()
- val latest by collectLastValue(underTest.lockScreenState)
+ val latest by collectLastValue(underTest.lockScreenState)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
- assertThat(visibleModel.icon)
- .isEqualTo(
- Icon.Loaded(
- drawable = ICON,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ assertThat(visibleModel.icon)
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = ICON,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ )
)
- )
- }
+ }
@Test
- fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = testScope.runTest {
+ fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
+ testScope.runTest {
setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
val latest by collectLastValue(underTest.lockScreenState)
@@ -104,54 +106,58 @@
}
@Test
- fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = testScope.runTest {
- setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
+ fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
+ testScope.runTest {
+ setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
- val latest by collectLastValue(underTest.lockScreenState)
+ val latest by collectLastValue(underTest.lockScreenState)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
- assertThat(visibleModel.icon)
- .isEqualTo(
- Icon.Loaded(
- drawable = ICON,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ assertThat(visibleModel.icon)
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = ICON,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ )
)
- )
- }
+ }
@Test
- fun affordance_walletFeatureNotEnabled_modelIsNone() = testScope.runTest {
- setUpState(isWalletFeatureAvailable = false)
+ fun affordance_walletFeatureNotEnabled_modelIsNone() =
+ testScope.runTest {
+ setUpState(isWalletFeatureAvailable = false)
- val latest by collectLastValue(underTest.lockScreenState)
+ val latest by collectLastValue(underTest.lockScreenState)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun affordance_queryNotSuccessful_modelIsNone() = testScope.runTest {
- setUpState(isWalletQuerySuccessful = false)
+ fun affordance_queryNotSuccessful_modelIsNone() =
+ testScope.runTest {
+ setUpState(isWalletQuerySuccessful = false)
- val latest by collectLastValue(underTest.lockScreenState)
+ val latest by collectLastValue(underTest.lockScreenState)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun affordance_noSelectedCard_modelIsNone() = testScope.runTest {
- setUpState(hasSelectedCard = false)
+ fun affordance_noSelectedCard_modelIsNone() =
+ testScope.runTest {
+ setUpState(hasSelectedCard = false)
- val latest by collectLastValue(underTest.lockScreenState)
+ val latest by collectLastValue(underTest.lockScreenState)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
fun onQuickAffordanceTriggered() {
- val animationController: ActivityLaunchAnimator.Controller = mock()
+ val animationController: ActivityTransitionAnimator.Controller = mock()
val expandable: Expandable = mock {
whenever(this.activityLaunchController()).thenReturn(animationController)
}
@@ -167,42 +173,46 @@
}
@Test
- fun getPickerScreenState_default() = testScope.runTest {
- setUpState()
+ fun getPickerScreenState_default() =
+ testScope.runTest {
+ setUpState()
- assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
- }
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
+ }
@Test
- fun getPickerScreenState_unavailable() = testScope.runTest {
- setUpState(
- isWalletServiceAvailable = false,
- )
+ fun getPickerScreenState_unavailable() =
+ testScope.runTest {
+ setUpState(
+ isWalletServiceAvailable = false,
+ )
- assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
- }
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
@Test
- fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest {
- setUpState(
- isWalletFeatureAvailable = false,
- )
+ fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() =
+ testScope.runTest {
+ setUpState(
+ isWalletFeatureAvailable = false,
+ )
- assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
- }
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
@Test
- fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest {
- setUpState(
- hasSelectedCard = false,
- )
+ fun getPickerScreenState_disabledWhenThereIsNoCard() =
+ testScope.runTest {
+ setUpState(
+ hasSelectedCard = false,
+ )
- assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
- }
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
private fun setUpState(
isWalletFeatureAvailable: Boolean = true,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 0543bc2..d52696a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -20,6 +20,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -68,6 +69,8 @@
@Before
fun setUp() {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
MockitoAnnotations.initMocks(this)
whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
kosmos.burnInInteractor = burnInInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6cc680b..c23ec22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,7 +59,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRootViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+ }
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val keyguardInteractor = kosmos.keyguardInteractor
@@ -111,6 +116,23 @@
}
@Test
+ fun iconContainer_isNotVisible_onKeyguard_dontShowWhenGoneToAodTransitionRunning() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope,
+ )
+ whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
+ runCurrent()
+
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
fun iconContainer_isVisible_bypassEnabled() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 4595fbf..7261723 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -18,22 +18,28 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,9 +55,7 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val underTest by lazy {
- createLockscreenSceneViewModel()
- }
+ private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
fun upTransitionSceneKey_canSwipeToUnlock_gone() =
@@ -80,29 +84,37 @@
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
}
+ @EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun leftTransitionSceneKey_communalIsEnabled_communal() =
testScope.runTest {
- kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
- val underTest = createLockscreenSceneViewModel()
-
- assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
+ with(kosmos.fakeUserRepository) {
+ setUserInfos(listOf(PRIMARY_USER))
+ setSelectedUserInfo(PRIMARY_USER)
+ }
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
+ assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
}
+ @DisableFlags(FLAG_COMMUNAL_HUB)
@Test
fun leftTransitionSceneKey_communalIsDisabled_null() =
testScope.runTest {
- kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
- val underTest = createLockscreenSceneViewModel()
-
- assertThat(underTest.leftDestinationSceneKey).isNull()
+ with(kosmos.fakeUserRepository) {
+ setUserInfos(listOf(PRIMARY_USER))
+ setSelectedUserInfo(PRIMARY_USER)
+ }
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
+ assertThat(leftDestinationSceneKey).isNull()
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
- communalInteractor = kosmos.communalInteractor,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
longPress =
KeyguardLongPressViewModel(
interactor = mock(),
@@ -110,4 +122,9 @@
notifications = kosmos.notificationsPlaceholderViewModel,
)
}
+
+ private companion object {
+ val PRIMARY_USER =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index b60f483..63fb67d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -50,7 +50,8 @@
@Test
fun mapsDisabledDataToInactiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val actualActivationState = tileState.activationState
@@ -59,7 +60,8 @@
@Test
fun mapsEnabledDataToActiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val actualActivationState = tileState.activationState
assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
@@ -67,7 +69,8 @@
@Test
fun mapsEnabledDataToOnIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
@@ -77,7 +80,8 @@
@Test
fun mapsDisabledDataToOffIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
@@ -86,11 +90,32 @@
}
@Test
- fun supportsOnlyClickAction() {
+ fun mapsUnavailableDataToOffIconState() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+ val actualIcon = tileState.icon()
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ }
+
+ @Test
+ fun supportClickActionWhenAvailable() {
val dontCare = true
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(dontCare))
val supportedActions = tileState.supportedActions
assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
}
+
+ @Test
+ fun doesNotSupportClickActionWhenUnavailable() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val supportedActions = tileState.supportedActions
+ assertThat(supportedActions).isEmpty()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index a9e39354..c5a8c70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -70,8 +70,7 @@
}
@Test
- fun dataMatchesController() = runTest {
- controller.setFlashlight(false)
+ fun isEnabledDataMatchesControllerWhenAvailable() = runTest {
val flowValues: List<FlashlightTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
@@ -81,8 +80,35 @@
controller.setFlashlight(false)
runCurrent()
- assertThat(flowValues.size).isEqualTo(3)
- assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup(), 2 from this test
+ assertThat(
+ flowValues.filterIsInstance<FlashlightTileModel.FlashlightAvailable>().map {
+ it.isEnabled
+ }
+ )
+ .containsExactly(false, false, true, false)
+ .inOrder()
+ }
+
+ /**
+ * Simulates the scenario of changes in flashlight tile availability when camera is initially
+ * closed, then opened, and closed again.
+ */
+ @Test
+ fun availabilityDataMatchesControllerAvailability() = runTest {
+ val flowValues: List<FlashlightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(false)
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(true)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup + 2 from this test
+ assertThat(flowValues.map { it is FlashlightTileModel.FlashlightAvailable })
+ .containsExactly(true, true, false, true)
+ .inOrder()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index 28d43b3..1f19c98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -29,7 +29,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
@@ -51,7 +53,7 @@
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = false
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
@@ -61,8 +63,17 @@
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = true
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
+
+ @Test
+ fun handleClickWhenUnavailable() = runTest {
+ assumeFalse(ActivityManager.isUserAMonkey())
+
+ underTest.handleInput(click(FlashlightTileModel.FlashlightTemporarilyUnavailable))
+
+ verify(controller, never()).setFlashlight(anyBoolean())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 42200a3..51f8b11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -61,7 +61,7 @@
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
- private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
private val footerActionsViewModel = mock<FooterActionsViewModel>()
private val footerActionsViewModelFactory =
mock<FooterActionsViewModel.Factory> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 9d3f0d6..006f429 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -40,7 +40,7 @@
import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.falsingCollector
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -130,7 +130,7 @@
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
- private val communalInteractor by lazy { kosmos.communalInteractor }
+ private val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor }
private val transitionState by lazy {
MutableStateFlow<ObservableTransitionState>(
@@ -155,7 +155,7 @@
LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
- communalInteractor = communalInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
longPress =
KeyguardLongPressViewModel(
interactor = mock(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
new file mode 100644
index 0000000..d3c6598
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.applicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.recents.utilities.Utilities
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeBackActionInteractorImplTest : SysuiTestCase() {
+ val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ val testScope = kosmos.testScope
+ val sceneInteractor = kosmos.sceneInteractor
+ val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ val underTest = kosmos.shadeBackActionInteractor
+
+ @Before
+ fun ignoreSplitShade() {
+ Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
+ }
+
+ @Test
+ fun animateCollapseQs_notOnQs() =
+ testScope.runTest {
+ setScene(SceneKey.Shade)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_entered() =
+ testScope.runTest {
+ enterDevice()
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_locked() =
+ testScope.runTest {
+ deviceEntryRepository.setUnlocked(false)
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseQs_notFullyCollapse() =
+ testScope.runTest {
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(false)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ private fun enterDevice() {
+ deviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ setScene(SceneKey.Gone)
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5ef095f..f1f5dc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -82,7 +82,7 @@
scope = testScope.backgroundScope,
)
- private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index cc4ebd4..c01f1c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -27,7 +27,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.LaunchableView
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.KeyguardViewMediator
@@ -78,7 +78,7 @@
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
@Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var statusBarWindowController: StatusBarWindowController
@Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
@@ -109,7 +109,7 @@
shadeAnimationInteractor,
Lazy { statusBarKeyguardViewManager },
Lazy { notifShadeWindowController },
- activityLaunchAnimator,
+ mActivityTransitionAnimator,
context,
DISPLAY_ID,
lockScreenUserManager,
@@ -149,7 +149,7 @@
override fun setShouldBlockVisibilityChanges(block: Boolean) {}
}
parent.addView(view)
- val controller = ActivityLaunchAnimator.Controller.fromView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
whenever(pendingIntent.isActivity).thenReturn(true)
whenever(keyguardStateController.isShowing).thenReturn(true)
whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
@@ -163,7 +163,7 @@
)
mainExecutor.runAllReady()
- verify(activityLaunchAnimator)
+ verify(mActivityTransitionAnimator)
.startPendingIntentWithAnimation(
nullable(),
eq(true),
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 6434209..1126ec3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -20,7 +20,7 @@
import android.os.UserHandle;
import android.view.View;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
@@ -54,7 +54,7 @@
*/
void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentUiThreadCallback,
- @Nullable ActivityLaunchAnimator.Controller animationController);
+ @Nullable ActivityTransitionAnimator.Controller animationController);
/**
* Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
@@ -64,7 +64,7 @@
*/
void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
@Nullable Runnable intentSentUiThreadCallback,
- @Nullable ActivityLaunchAnimator.Controller animationController);
+ @Nullable ActivityTransitionAnimator.Controller animationController);
/**
* The intent flag can be specified in startActivity().
@@ -72,26 +72,26 @@
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
void startActivity(Intent intent, boolean dismissShade);
default void startActivity(Intent intent, boolean dismissShade,
- @Nullable ActivityLaunchAnimator.Controller animationController) {
+ @Nullable ActivityTransitionAnimator.Controller animationController) {
startActivity(intent, dismissShade, animationController,
false /* showOverLockscreenWhenLocked */);
}
void startActivity(Intent intent, boolean dismissShade,
- @Nullable ActivityLaunchAnimator.Controller animationController,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked);
void startActivity(Intent intent, boolean dismissShade,
- @Nullable ActivityLaunchAnimator.Controller animationController,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked, UserHandle userHandle);
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
void startActivity(Intent intent, boolean dismissShade, Callback callback);
void postStartActivityDismissingKeyguard(Intent intent, int delay);
void postStartActivityDismissingKeyguard(Intent intent, int delay,
- @Nullable ActivityLaunchAnimator.Controller animationController);
+ @Nullable ActivityTransitionAnimator.Controller animationController);
/** Posts a start activity intent that dismisses keyguard. */
void postStartActivityDismissingKeyguard(Intent intent, int delay,
- @Nullable ActivityLaunchAnimator.Controller animationController,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
@Nullable String customMessage);
void postStartActivityDismissingKeyguard(PendingIntent intent);
@@ -100,7 +100,7 @@
* animation controller that should be used for the activity launch animation.
*/
void postStartActivityDismissingKeyguard(PendingIntent intent,
- @Nullable ActivityLaunchAnimator.Controller animationController);
+ @Nullable ActivityTransitionAnimator.Controller animationController);
void postQSRunnableDismissingKeyguard(Runnable runnable);
@@ -123,7 +123,7 @@
boolean disallowEnterPictureInPictureWhileLaunching,
Callback callback,
int flags,
- @Nullable ActivityLaunchAnimator.Controller animationController,
+ @Nullable ActivityTransitionAnimator.Controller animationController,
UserHandle userHandle);
/** Execute a runnable after dismissing keyguard. */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 4436be7..fd7a7f3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -116,6 +116,8 @@
/** Custom constraints to apply to Lockscreen ConstraintLayout. */
fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+ fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
}
/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
@@ -131,6 +133,10 @@
}
return constraints
}
+
+ override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
+ return constraints
+ }
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
index dec9930..a80d3b4 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -20,6 +20,7 @@
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
+ android:alpha="0.3"
>
<path
android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2d08841..b30e4a2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1599,6 +1599,15 @@
<!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
<string name="accessibility_status_bar_hotspot">Hotspot</string>
+ <!-- Accessibility label for no satellite connection [CHAR LIMIT=NONE] -->
+ <string name="accessibility_status_bar_satellite_no_connection">Satellite, no connection</string>
+ <!-- Accessibility label for poor satellite connection [CHAR LIMIT=NONE] -->
+ <string name="accessibility_status_bar_satellite_poor_connection">Satellite, poor connection</string>
+ <!-- Accessibility label for good satellite connection [CHAR LIMIT=NONE] -->
+ <string name="accessibility_status_bar_satellite_good_connection">Satellite, good connection</string>
+ <!-- Accessibility label for available satellite connection [CHAR LIMIT=NONE] -->
+ <string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
+
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
@@ -3337,6 +3346,6 @@
<string name="keyboard_backlight_value">Level %1$d of %2$d</string>
<!-- Label for home control panel [CHAR LIMIT=30] -->
<string name="home_controls_dream_label">Home Controls</string>
- <!-- Description for home control panel [CHAR LIMIT=50] -->
+ <!-- Description for home control panel [CHAR LIMIT=67] -->
<string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 1a10c7a..458a21c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -38,7 +38,6 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.log.BouncerLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -212,7 +211,6 @@
private final FeatureFlags mFeatureFlags;
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
- private final KeyboardRepository mKeyboardRepository;
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
@Inject
@@ -228,7 +226,6 @@
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
UiEventLogger uiEventLogger,
- KeyboardRepository keyboardRepository,
KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
@@ -246,7 +243,6 @@
mFeatureFlags = featureFlags;
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
- mKeyboardRepository = keyboardRepository;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
}
@@ -277,7 +273,7 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
- mUiEventLogger, mKeyboardRepository
+ mUiEventLogger, mKeyguardKeyboardInteractor
);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
@@ -285,14 +281,15 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyboardRepository);
+ mKeyguardKeyboardInteractor);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyboardRepository);
+ mKeyguardKeyboardInteractor
+ );
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 60dd568..476497d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,8 +16,8 @@
package com.android.keyguard;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
@@ -32,9 +32,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -43,7 +43,7 @@
private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
- private final KeyboardRepository mKeyboardRepository;
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
protected PasswordTextView mPasswordEntry;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -75,13 +75,13 @@
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
- KeyboardRepository keyboardRepository) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
- mKeyboardRepository = keyboardRepository;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
}
@@ -132,7 +132,7 @@
okButton.setOnHoverListener(mLiftToActivateListener);
}
if (pinInputFieldStyledFocusState()) {
- collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+ collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
this::setKeyboardBasedFocusOutline);
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b958f55..f4cda02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -25,10 +25,10 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -61,11 +61,11 @@
FalsingCollector falsingCollector,
DevicePostureController postureController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
- KeyboardRepository keyboardRepository) {
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyboardRepository);
+ keyguardKeyboardInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 1cdcbd0..558679e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -42,9 +42,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -94,11 +94,12 @@
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyboardRepository);
+ keyguardKeyboardInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index f019d61..cb1c4b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,9 +37,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -91,11 +91,12 @@
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyboardRepository);
+ keyguardKeyboardInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ef65144..9ebae90 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -109,8 +109,12 @@
animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
- PropertyAnimator.setProperty(
- mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 1");
+ } else {
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ }
} else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
mView.setVisibility(View.VISIBLE);
mKeyguardViewVisibilityAnimating = true;
@@ -179,9 +183,13 @@
mView.setVisibility(View.VISIBLE);
}
} else {
- log("Direct set Visibility to GONE");
- mView.setVisibility(View.GONE);
- mView.setAlpha(1f);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 2");
+ } else {
+ log("Direct set Visibility to GONE");
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
}
mLastOccludedState = isOccluded;
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 7a560e8..29df49b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -29,26 +29,28 @@
import javax.inject.Inject
/**
- * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for
- * screen on events, this will invoke the onDrawn Runnable after all tasks have completed. This
- * should route back to the [com.android.systemui.keyguard.KeyguardService], which informs
- * the system_server that keyguard has drawn.
+ * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for screen
+ * on events, this will invoke the onDrawn Runnable after all tasks have completed. This should
+ * route back to the [com.android.systemui.keyguard.KeyguardService], which informs the
+ * system_server that keyguard has drawn.
*/
@SysUISingleton
-class ScreenOnCoordinator @Inject constructor(
+class ScreenOnCoordinator
+@Inject
+constructor(
unfoldComponent: Optional<SysUIUnfoldComponent>,
- @Main private val mainHandler: Handler
+ @Main private val mainHandler: Handler,
) {
- private val unfoldLightRevealAnimation = unfoldComponent.map(
- SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation).getOrNull()
- private val foldAodAnimationController = unfoldComponent.map(
- SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
+ private val foldAodAnimationController =
+ unfoldComponent.map(SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
+ private val fullScreenLightRevealAnimations =
+ unfoldComponent.map(SysUIUnfoldComponent::getFullScreenLightRevealAnimations).getOrNull()
private val pendingTasks = PendingTasksContainer()
/**
- * When turning on, registers tasks that may need to run before invoking [onDrawn].
- * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
+ * When turning on, registers tasks that may need to run before invoking [onDrawn]. This is
+ * called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
*/
@BinderThread
fun onScreenTurningOn(onDrawn: Runnable) {
@@ -56,8 +58,10 @@
pendingTasks.reset()
- unfoldLightRevealAnimation?.onScreenTurningOn(pendingTasks.registerTask("unfold-reveal"))
foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod"))
+ fullScreenLightRevealAnimations?.forEach {
+ it.onScreenTurningOn(pendingTasks.registerTask(it::class.java.simpleName))
+ }
pendingTasks.onTasksComplete {
if (Flags.enableBackgroundKeyguardOndrawnCallback()) {
@@ -71,8 +75,8 @@
}
/**
- * Called when screen is fully turned on and screen on blocker is removed.
- * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
+ * Called when screen is fully turned on and screen on blocker is removed. This is called on a
+ * binder thread from [com.android.systemui.keyguard.KeyguardService].
*/
@BinderThread
fun onScreenTurnedOn() {
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 4c9782c..39e1c41 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -36,7 +36,7 @@
* If your CoreStartable depends on different CoreStartables starting before it, use a
* {@link com.android.systemui.startable.Dependencies} annotation to list out those dependencies.
*
- * @see SystemUIApplication#startServicesIfNeeded()
+ * @see SystemUIApplication#startSystemUserServicesIfNeeded()
*/
public interface CoreStartable extends Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8aae206..15ef61e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,7 @@
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -77,6 +78,7 @@
private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
private SysUIComponent mSysUIComponent;
private SystemUIInitializer mInitializer;
+ private ProcessWrapper mProcessWrapper;
public SystemUIApplication() {
super();
@@ -115,6 +117,7 @@
// Enable Looper trace points.
// This allows us to see Handler callbacks on traces.
rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+ mProcessWrapper = rootComponent.getProcessWrapper();
// Set the application theme that is inherited by all services. Note that setting the
// application theme in the manifest does only work for activities. Keep this in sync with
@@ -132,7 +135,7 @@
View.setTraceLayoutSteps(true);
}
- if (rootComponent.getProcessWrapper().isSystemUser()) {
+ if (mProcessWrapper.isSystemUser()) {
IntentFilter bootCompletedFilter = new
IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -199,7 +202,11 @@
* <p>This method must only be called from the main thread.</p>
*/
- public void startServicesIfNeeded() {
+ public void startSystemUserServicesIfNeeded() {
+ if (!mProcessWrapper.isSystemUser()) {
+ Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser");
+ return; // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+ }
final String vendorComponent = mInitializer.getVendorComponent(getResources());
// Sort the startables so that we get a deterministic ordering.
@@ -219,6 +226,9 @@
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
+ if (mProcessWrapper.isSystemUser()) {
+ return; // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+ }
// Sort the startables so that we get a deterministic ordering.
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 872b005..1a9b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,10 +22,10 @@
import android.os.HandlerThread;
import android.util.Log;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.res.R;
import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.wm.shell.keyguard.KeyguardTransitions;
@@ -124,9 +124,6 @@
.setDesktopMode(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
- if (initializeComponents) {
- mSysUIComponent.init();
- }
// Every other part of our codebase currently relies on Dependency, so we
// really need to ensure the Dependency gets initialized early on.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index f4ec6f7..407f764 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -19,12 +19,31 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.process.ProcessWrapper;
+
+import javax.inject.Inject;
public class SystemUISecondaryUserService extends Service {
+ private static final String TAG = "SysUISecondaryService";
+
+ private final ProcessWrapper mProcessWrapper;
+
+ @Inject
+ SystemUISecondaryUserService(ProcessWrapper processWrapper) {
+ mProcessWrapper = processWrapper;
+ }
+
@Override
public void onCreate() {
super.onCreate();
+ if (mProcessWrapper.isSystemUser()) {
+ Log.w(TAG, "SecondaryServices started for System User. Stopping it.");
+ stopSelf();
+ return;
+ }
((SystemUIApplication) getApplication()).startSecondaryUserServicesIfNeeded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 76c2282..b26be0c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -77,7 +77,7 @@
super.onCreate();
// Start all of SystemUI
- ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+ ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index bf121fb..e57323b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -26,6 +26,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.text.Layout;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -162,6 +163,7 @@
mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
mTextView.setTextColor(textColor);
+ mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
final ColorStateList colorAccent = Utils.getColorAccent(getContext());
mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 6076f32..5bd7e54 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -29,7 +29,7 @@
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@
}
override fun onBackProgressed(backEvent: BackEvent) {
- if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
- shadeViewController.onBackProgressed(backEvent.progress)
+ if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
+ shadeBackActionInteractor.onBackProgressed(backEvent.progress)
}
}
}
@@ -77,12 +77,12 @@
get() =
notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
- private lateinit var shadeViewController: ShadeViewController
+ private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
private lateinit var qsController: QuickSettingsController
- fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+ fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
this.qsController = qsController
- this.shadeViewController = svController
+ this.shadeBackActionInteractor = svController
}
override fun start() {
@@ -114,16 +114,16 @@
return true
}
if (qsController.expanded) {
- shadeViewController.animateCollapseQs(false)
+ shadeBackActionInteractor.animateCollapseQs(false)
return true
}
- if (shadeViewController.closeUserSwitcherIfOpen()) {
+ if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
return true
}
if (shouldBackBeHandled()) {
- if (shadeViewController.canBeCollapsed()) {
+ if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
- shadeViewController.onBackPressed()
+ shadeBackActionInteractor.onBackPressed()
shadeController.animateCollapseShade()
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 66fe4b3..716209d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -66,7 +66,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
@@ -162,7 +162,7 @@
mUnlockedScreenOffAnimationController;
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
- @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ @NonNull private final ActivityTransitionAnimator mActivityTransitionAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@NonNull private final ShadeInteractor mShadeInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
@@ -287,7 +287,7 @@
event,
fromUdfpsView
),
- mActivityLaunchAnimator,
+ mActivityTransitionAnimator,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
@@ -663,7 +663,7 @@
@NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
- @NonNull ActivityLaunchAnimator activityLaunchAnimator,
+ @NonNull ActivityTransitionAnimator activityTransitionAnimator,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
@NonNull ShadeInteractor shadeInteractor,
@@ -706,7 +706,7 @@
mSystemClock = systemClock;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityTransitionAnimator = activityTransitionAnimator;
mSensorProps = new FingerprintSensorPropertiesInternal(
-1 /* sensorId */,
SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4ea5f4c..a209eae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,7 +44,7 @@
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder
@@ -100,7 +100,7 @@
@RequestReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
@@ -304,7 +304,7 @@
unlockedScreenOffAnimationController,
dialogManager,
controller,
- activityLaunchAnimator,
+ activityTransitionAnimator,
primaryBouncerInteractor,
alternateBouncerInteractor,
udfpsKeyguardAccessibilityDelegate,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 7020d05..018d92e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -25,7 +25,7 @@
import com.android.app.animation.Interpolators
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -69,7 +69,7 @@
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
@@ -137,20 +137,20 @@
}
}
- private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
- object : ActivityLaunchAnimator.Listener {
- override fun onLaunchAnimationStart() {
+ private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener =
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
isLaunchingActivity = true
activityLaunchProgress = 0f
updateAlpha()
}
- override fun onLaunchAnimationEnd() {
+ override fun onTransitionAnimationEnd() {
isLaunchingActivity = false
updateAlpha()
}
- override fun onLaunchAnimationProgress(linearProgress: Float) {
+ override fun onTransitionAnimationProgress(linearProgress: Float) {
activityLaunchProgress = linearProgress
updateAlpha()
}
@@ -370,7 +370,7 @@
updatePauseAuth()
keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
- activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener)
view.startIconAsyncInflate {
val animationViewInternal: View =
view.requireViewById(R.id.udfps_animation_view_internal)
@@ -389,7 +389,7 @@
if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
}
- activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+ activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener)
keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
}
@@ -536,7 +536,7 @@
val udfpsActivityLaunchAlphaMultiplier =
1f -
(activityLaunchProgress *
- (ActivityLaunchAnimator.TIMINGS.totalDuration / 83))
+ (ActivityTransitionAnimator.TIMINGS.totalDuration / 83))
.coerceIn(0f, 1f)
alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
new file mode 100644
index 0000000..d235c95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.shared.model
+
+import android.annotation.AttrRes
+import android.annotation.ColorInt
+
+/**
+ * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
+ * [Color.Attribute]
+ */
+sealed interface Color {
+
+ data class Loaded(@ColorInt val color: Int) : Color
+
+ data class Attribute(@AttrRes val attribute: Int) : Color
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 12be32c..964eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -24,17 +24,12 @@
import androidx.annotation.LayoutRes
import com.android.settingslib.Utils
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
import com.android.systemui.statusbar.policy.onThemeChanged
import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.view.bindLatest
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -91,46 +86,3 @@
.map { layoutInflater.inflate(id, root, attachToRoot) as T }
}
}
-
-/**
- * Perform an inflation right away, then re-inflate whenever the device configuration changes, and
- * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
- * [onInflate] when done.
- *
- * This never completes unless cancelled, it just suspends and waits for updates. It runs on a
- * background thread using [backgroundDispatcher].
- *
- * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
- *
- * An example use-case of this is when a view needs to be re-inflated whenever a configuration
- * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
- * code in the parent view's binder would look like:
- * ```
- * parentView.repeatWhenAttached {
- * configurationState
- * .reinflateAndBindLatest(
- * R.layout.my_layout,
- * parentView,
- * attachToRoot = false,
- * coroutineScope = lifecycleScope,
- * configurationController.onThemeChanged,
- * ) { view: ChildView ->
- * ChildViewBinder.bind(view, childViewModel)
- * }
- * }
- * ```
- *
- * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
- * [DisposableHandle].
- */
-suspend fun <T : View> ConfigurationState.reinflateAndBindLatest(
- @LayoutRes resource: Int,
- root: ViewGroup?,
- attachToRoot: Boolean,
- backgroundDispatcher: CoroutineDispatcher,
- onInflate: (T) -> DisposableHandle?,
-) {
- inflateLayout<T>(resource, root, attachToRoot)
- .flowOn(backgroundDispatcher)
- .bindLatest(onInflate)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index dc07c1b..0bad33b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -20,6 +20,7 @@
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -36,6 +37,7 @@
CommunalWidgetRepositoryModule::class,
CommunalDatabaseModule::class,
CommunalPrefsRepositoryModule::class,
+ CommunalSettingsRepositoryModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
new file mode 100644
index 0000000..83a5bdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import java.util.EnumSet
+
+/** Reasons that communal is disabled, primarily for logging. */
+enum class DisabledReason(val loggingString: String) {
+ /** Communal should be disabled due to invalid current user */
+ DISABLED_REASON_INVALID_USER("invalidUser"),
+ /** Communal should be disabled due to the flag being off */
+ DISABLED_REASON_FLAG("flag"),
+ /** Communal should be disabled because the user has turned off the setting */
+ DISABLED_REASON_USER_SETTING("userSetting"),
+ /** Communal is disabled by the device policy app */
+ DISABLED_REASON_DEVICE_POLICY("devicePolicy"),
+}
+
+/**
+ * Model representing the reasons communal hub should be disabled. Allows logging reasons separately
+ * for debugging.
+ */
+@JvmInline
+value class CommunalEnabledState(
+ private val disabledReasons: EnumSet<DisabledReason> =
+ EnumSet.noneOf(DisabledReason::class.java)
+) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons {
+
+ /** Creates [CommunalEnabledState] with a single reason for being disabled */
+ constructor(reason: DisabledReason) : this(EnumSet.of(reason))
+
+ /** Checks if there are any reasons communal should be disabled. If none, returns true. */
+ val enabled: Boolean
+ get() = isEmpty()
+
+ override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) {
+ for (reason in DisabledReason.entries) {
+ val newVal = contains(reason)
+ if (newVal != prevVal.contains(reason)) {
+ row.logChange(
+ columnName = reason.loggingString,
+ value = newVal,
+ )
+ }
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ for (reason in DisabledReason.entries) {
+ row.logChange(columnName = reason.loggingString, value = contains(reason))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index addd880..4a06585f5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -16,22 +16,14 @@
package com.android.systemui.communal.data.repository
-import com.android.systemui.Flags.communalHub
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -39,26 +31,13 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
- /** Whether communal features are enabled. */
- val isCommunalEnabled: Boolean
-
- /**
- * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
- * settings).
- */
- val communalEnabledState: StateFlow<Boolean>
-
/** Whether the communal hub is showing. */
val isCommunalHubShowing: Flow<Boolean>
@@ -87,37 +66,11 @@
class CommunalRepositoryImpl
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
@Background backgroundScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val featureFlagsClassic: FeatureFlagsClassic,
sceneContainerFlags: SceneContainerFlags,
sceneContainerRepository: SceneContainerRepository,
- userRepository: UserRepository,
- private val secureSettings: SecureSettings
) : CommunalRepository {
- private val communalEnabledSettingState: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
- .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
-
- override val communalEnabledState: StateFlow<Boolean> =
- if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
- communalEnabledSettingState
- .filterNotNull()
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = true
- )
- } else {
- MutableStateFlow(false)
- }
-
- override val isCommunalEnabled: Boolean
- get() = communalEnabledState.value
-
private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT)
override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
@@ -153,26 +106,4 @@
} else {
desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
}
-
- private fun observeSettings(userId: Int): Flow<Boolean> =
- secureSettings
- .observerFlow(
- userId = userId,
- names =
- arrayOf(
- GLANCEABLE_HUB_ENABLED,
- )
- )
- // Force an update
- .onStart { emit(Unit) }
- .map { readFromSettings(userId) }
-
- private suspend fun readFromSettings(userId: Int): Boolean =
- withContext(backgroundDispatcher) {
- secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
- }
-
- companion object {
- private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
new file mode 100644
index 0000000..201b049
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.util.EnumSet
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface CommunalSettingsRepository {
+ /** A [CommunalEnabledState] for the specified user. */
+ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+}
+
+@SysUISingleton
+class CommunalSettingsRepositoryImpl
+@Inject
+constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val featureFlagsClassic: FeatureFlagsClassic,
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val devicePolicyManager: DevicePolicyManager,
+) : CommunalSettingsRepository {
+
+ private val flagEnabled: Boolean by lazy {
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ }
+
+ override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
+ if (!user.isMain) {
+ return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER))
+ }
+ if (!flagEnabled) {
+ return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG))
+ }
+ return combine(
+ getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING),
+ getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY),
+ ) { reasons ->
+ reasons.filterNotNull()
+ }
+ .map { reasons ->
+ if (reasons.isEmpty()) {
+ EnumSet.noneOf(DisabledReason::class.java)
+ } else {
+ EnumSet.copyOf(reasons)
+ }
+ }
+ .map { reasons -> CommunalEnabledState(reasons) }
+ .flowOn(bgDispatcher)
+ }
+
+ private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
+ secureSettings
+ .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_ENABLED))
+ // Force an update
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(
+ GLANCEABLE_HUB_ENABLED,
+ ENABLED_SETTING_DEFAULT,
+ user.id,
+ ) == 1
+ }
+
+ private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ user = user.userHandle
+ )
+ .emitOnStart()
+ .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
+
+ companion object {
+ const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ private const val ENABLED_SETTING_DEFAULT = 1
+ }
+}
+
+private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
+ (getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
+
+private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled ->
+ if (enabled) null else reason
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt
index 128f58b..a931d3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.animation
+package com.android.systemui.communal.data.repository
-import com.android.systemui.kosmos.Kosmos
+import dagger.Binds
+import dagger.Module
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+@Module
+interface CommunalSettingsRepositoryModule {
+ @Binds
+ fun communalSettingsRepository(impl: CommunalSettingsRepositoryImpl): CommunalSettingsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 950ac3c..23f590e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -42,7 +42,6 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
-import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
@@ -74,8 +73,8 @@
private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
- userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
@CommunalLog logBuffer: LogBuffer,
@@ -90,13 +89,12 @@
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
- get() = communalRepository.isCommunalEnabled
+ get() = communalSettingsInteractor.isCommunalEnabled.value
/** Whether communal features are enabled and available. */
val isCommunalAvailable: Flow<Boolean> =
and(
- communalRepository.communalEnabledState,
- userRepository.selectedUserInfo.map { it.isMain },
+ communalSettingsInteractor.isCommunalEnabled,
not(keyguardInteractor.isEncryptedOrLockdown),
or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
new file mode 100644
index 0000000..0b096ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.repository.CommunalSettingsRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalSettingsInteractor
+@Inject
+constructor(
+ @Background private val bgScope: CoroutineScope,
+ private val repository: CommunalSettingsRepository,
+ userInteractor: SelectedUserInteractor,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
+) {
+ /** Whether or not communal is enabled for the currently selected user. */
+ val isCommunalEnabled: StateFlow<Boolean> =
+ userInteractor.selectedUserInfo
+ .flatMapLatest { user -> repository.getEnabledState(user) }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "disabledReason",
+ initialValue = CommunalEnabledState()
+ )
+ .map { model -> model.enabled }
+ // Start this eagerly since the value is accessed synchronously in many places.
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 1404ee2..25dfc02 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -28,17 +28,18 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.launch
/** Encapsulates business-logic related to communal tutorial state. */
@@ -51,6 +52,7 @@
private val communalTutorialRepository: CommunalTutorialRepository,
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
communalInteractor: CommunalInteractor,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
@@ -110,20 +112,24 @@
return null
}
- private var job: Job? = null
private fun listenForTransitionToUpdateTutorialState() {
- if (!communalRepository.isCommunalEnabled) {
- return
- }
- job =
- scope.launch {
- tutorialStateToUpdate.collect {
- communalTutorialRepository.setTutorialState(it)
- if (it == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
- job?.cancel()
+ scope.launch {
+ communalSettingsInteractor.isCommunalEnabled
+ .flatMapLatest { enabled ->
+ if (!enabled) {
+ emptyFlow()
+ } else {
+ tutorialStateToUpdate
}
}
- }
+ .transformWhile { tutorialState ->
+ emit(tutorialState)
+ tutorialState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+ }
+ .collect { tutorialState ->
+ communalTutorialRepository.setTutorialState(tutorialState)
+ }
+ }
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index afa7fa9..4c1e77b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -19,7 +19,7 @@
import android.app.PendingIntent
import android.view.View
import android.widget.RemoteViews
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.ui.view.getNearestParent
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -42,7 +42,7 @@
private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean {
val hostView = view.getNearestParent<CommunalAppWidgetHostView>()
- val animationController = hostView?.let(ActivityLaunchAnimator.Controller::fromView)
+ val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView)
activityStarter.startPendingIntentMaybeDismissingKeyguard(
pendingIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 9d4ed20..afa2375 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -35,7 +35,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.Utils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -275,8 +275,9 @@
.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
.putExtra(ControlsUiController.EXIT_TO_DREAM, true);
- final ActivityLaunchAnimator.Controller controller =
- v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+ final ActivityTransitionAnimator.Controller controller =
+ v != null
+ ? ActivityTransitionAnimator.Controller.fromView(v, null /* cujType */)
: null;
if (mControlsComponent.getVisibility() == AVAILABLE) {
// Controls can be made visible.
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index dd186d6..f7bc5cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -26,10 +26,13 @@
import com.android.systemui.ScreenDecorationsModule;
import com.android.systemui.accessibility.SystemActionsModule;
import com.android.systemui.battery.BatterySaverModule;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.media.dagger.MediaModule;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.navigationbar.NavigationBarControllerModule;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
@@ -63,6 +66,7 @@
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.toast.ToastModule;
+import com.android.systemui.unfold.SysUIUnfoldStartableModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -92,12 +96,15 @@
AospPolicyModule.class,
BatterySaverModule.class,
CollapsedStatusBarFragmentStartableModule.class,
+ ConnectingDisplayViewModel.StartableModule.class,
GestureModule.class,
HeadsUpModule.class,
KeyboardShortcutsModule.class,
MediaModule.class,
+ MediaMuteAwaitConnectionCli.StartableModule.class,
MultiUserUtilsModule.class,
NavigationBarControllerModule.class,
+ NearbyMediaDevicesManager.StartableModule.class,
PowerModule.class,
QSModule.class,
RearDisplayModule.class,
@@ -108,6 +115,7 @@
ShadeModule.class,
StartCentralSurfacesModule.class,
SceneContainerFrameworkModule.class,
+ SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
VolumeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index e7b8773..3b0c281 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -19,25 +19,15 @@
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dependency;
-import com.android.systemui.Flags;
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.PerUser;
-import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.unfold.FoldStateLogger;
-import com.android.systemui.unfold.FoldStateLoggingProvider;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.dagger.UnfoldBg;
-import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -126,42 +116,6 @@
}
/**
- * Initializes all the SysUI components.
- */
- default void init() {
- // Initialize components that have no direct tie to the dagger dependency graph,
- // but are critical to this component's operation
- getSysUIUnfoldComponent()
- .ifPresent(
- c -> {
- c.getUnfoldLightRevealOverlayAnimation().init();
- c.getUnfoldTransitionWallpaperController().init();
- c.getUnfoldHapticsPlayer();
- c.getNaturalRotationUnfoldProgressProvider().init();
- c.getUnfoldLatencyTracker().init();
- });
- // No init method needed, just needs to be gotten so that it's created.
- getMediaMuteAwaitConnectionCli();
- getNearbyMediaDevicesManager();
- getConnectingDisplayViewModel().init();
- getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
- getFoldStateLogger().ifPresent(FoldStateLogger::init);
-
- Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider;
-
- if (Flags.unfoldAnimationBackgroundProgress()) {
- unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider();
- } else {
- unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider();
- }
- unfoldTransitionProgressProvider
- .ifPresent(
- (progressProvider) ->
- getUnfoldTransitionProgressForwarder()
- .ifPresent(progressProvider::addCallback));
- }
-
- /**
* Provides a BootCompleteCache.
*/
@SysUISingleton
@@ -180,37 +134,6 @@
ContextComponentHelper getContextComponentHelper();
/**
- * Creates a UnfoldTransitionProgressProvider that calculates progress in the background.
- */
- @SysUISingleton
- @UnfoldBg
- Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider();
-
- /**
- * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread.
- */
- @SysUISingleton
- Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
-
- /**
- * Creates a UnfoldTransitionProgressForwarder.
- */
- @SysUISingleton
- Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder();
-
- /**
- * Creates a FoldStateLoggingProvider.
- */
- @SysUISingleton
- Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider();
-
- /**
- * Creates a FoldStateLogger.
- */
- @SysUISingleton
- Optional<FoldStateLogger> getFoldStateLogger();
-
- /**
* Main dependency providing module.
*/
@SysUISingleton
@@ -227,22 +150,6 @@
InitController getInitController();
/**
- * For devices with a hinge: access objects within this component
- */
- Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent();
-
- /** */
- MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
-
- /** */
- NearbyMediaDevicesManager getNearbyMediaDevicesManager();
-
- /**
- * Creates a ConnectingDisplayViewModel
- */
- ConnectingDisplayViewModel getConnectingDisplayViewModel();
-
- /**
* Returns {@link CoreStartable}s that should be started with the application.
*/
Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 28fd9a9..1720de8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import android.app.INotificationManager;
+import android.app.Service;
import android.content.Context;
import android.service.dreams.IDreamManager;
@@ -28,6 +29,7 @@
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CameraProtectionModule;
+import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
@@ -150,6 +152,8 @@
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.Collections;
import java.util.Optional;
@@ -384,4 +388,9 @@
@Binds
abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
LargeScreenShadeInterpolatorImpl impl);
+
+ @Binds
+ @IntoMap
+ @ClassKey(SystemUISecondaryUserService.class)
+ abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 684627b..2461c26 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -39,4 +39,6 @@
/** Provide the current status of fingerprint authentication. */
val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
repository.authenticationStatus
+
+ val isLockedOut: Flow<Boolean> = repository.isLockedOut
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 98130eb..cf91e14 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -36,7 +36,6 @@
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -78,7 +77,7 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val faceAuthenticationLogger: FaceAuthenticationLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val userRepository: UserRepository,
private val facePropertyRepository: FacePropertyRepository,
private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
@@ -149,14 +148,24 @@
}
.launchIn(applicationScope)
- deviceEntryFingerprintAuthRepository.isLockedOut
- .onEach {
- if (it) {
+ deviceEntryFingerprintAuthInteractor.isLockedOut
+ .sample(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, ::Pair)
+ .filter { (_, faceEnabledAndEnrolled) ->
+ // We don't care about this if face auth is not enabled.
+ faceEnabledAndEnrolled
+ }
+ .map { (fpLockedOut, _) -> fpLockedOut }
+ .sample(userRepository.selectedUser, ::Pair)
+ .onEach { (fpLockedOut, currentUser) ->
+ if (fpLockedOut) {
faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
- // We don't care about this if face auth is not enabled.
if (isFaceAuthEnabledAndEnrolled()) {
repository.setLockedOut(true)
}
+ } else {
+ // Fingerprint is not locked out anymore, revert face lockout state back to
+ // previous value.
+ resetLockedOutState(currentUser.userInfo.id)
}
}
.launchIn(applicationScope)
@@ -169,10 +178,7 @@
val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
if (wasSwitching && !isSwitching) {
- val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
- repository.setLockedOut(
- lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
- )
+ resetLockedOutState(curr.userInfo.id)
yield()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -185,6 +191,13 @@
.launchIn(applicationScope)
}
+ private suspend fun resetLockedOutState(currentUserId: Int) {
+ val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
+ repository.setLockedOut(
+ lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
+ )
+ }
+
override fun onSwipeUpOnBouncer() {
runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 10aa703..190062c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -18,6 +18,7 @@
import android.app.Dialog
import android.content.Context
import com.android.server.policy.feature.flags.Flags
+import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -26,6 +27,10 @@
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.MirroringConfirmationDialog
import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -47,12 +52,12 @@
@Application private val scope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
private val configurationController: ConfigurationController,
-) {
+) : CoreStartable {
private var dialog: Dialog? = null
/** Starts listening for pending displays. */
- fun init() {
+ override fun start() {
val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
val concurrentDisplaysInProgessFlow =
if (Flags.enableDualDisplayBlocking()) {
@@ -96,4 +101,12 @@
dialog?.hide()
dialog = null
}
+
+ @Module
+ interface StartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(ConnectingDisplayViewModel::class)
+ fun bindsConnectingDisplayViewModel(impl: ConnectingDisplayViewModel): CoreStartable
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt
new file mode 100644
index 0000000..304fdd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.haptics.slider
+
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.awaitCancellation
+
+object HapticSliderViewBinder {
+ /**
+ * Binds a [SeekableSliderHapticPlugin] to a [View]. The binded view should be a
+ * [android.widget.SeekBar] or a container of a [android.widget.SeekBar]
+ */
+ @JvmStatic
+ fun bind(view: View?, plugin: SeekableSliderHapticPlugin) {
+ view?.repeatWhenAttached {
+ plugin.startInScope(lifecycleScope)
+ try {
+ awaitCancellation()
+ } finally {
+ plugin.stop()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 58fb6a9..931a869 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -20,11 +20,8 @@
import android.view.VelocityTracker
import android.widget.SeekBar
import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -43,10 +40,8 @@
constructor(
vibratorHelper: VibratorHelper,
systemClock: SystemClock,
- @Main private val mainDispatcher: CoroutineDispatcher,
- @Application private val applicationScope: CoroutineScope,
sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
- sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
+ private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
) {
private val velocityTracker = VelocityTracker.obtain()
@@ -61,19 +56,15 @@
systemClock,
)
- private val sliderTracker =
- SeekableSliderTracker(
- sliderHapticFeedbackProvider,
- sliderEventProducer,
- mainDispatcher,
- sliderTrackerConfig,
- )
+ private var sliderTracker: SeekableSliderTracker? = null
+
+ private var pluginScope: CoroutineScope? = null
val isTracking: Boolean
- get() = sliderTracker.isTracking
+ get() = sliderTracker?.isTracking == true
- val trackerState: SliderState
- get() = sliderTracker.currentState
+ val trackerState: SliderState?
+ get() = sliderTracker?.currentState
/**
* A waiting [Job] for a timer that estimates the key-up event when a key-down event is
@@ -89,14 +80,20 @@
get() = keyUpJob != null && keyUpJob?.isActive == true
/**
- * Start the plugin.
- *
- * This starts the tracking of slider states, events and triggering of haptic feedback.
+ * Specify the scope for the plugin's operations and start the slider tracker in this scope.
+ * This also involves the key-up timer job.
*/
- fun start() {
- if (!isTracking) {
- sliderTracker.startTracking()
- }
+ fun startInScope(scope: CoroutineScope) {
+ if (sliderTracker != null) stop()
+ sliderTracker =
+ SeekableSliderTracker(
+ sliderHapticFeedbackProvider,
+ sliderEventProducer,
+ scope,
+ sliderTrackerConfig,
+ )
+ pluginScope = scope
+ sliderTracker?.startTracking()
}
/**
@@ -104,7 +101,7 @@
*
* This stops the tracking of slider states, events and triggers of haptic feedback.
*/
- fun stop() = sliderTracker.stopTracking()
+ fun stop() = sliderTracker?.stopTracking()
/** React to a touch event */
fun onTouchEvent(event: MotionEvent?) {
@@ -147,9 +144,9 @@
/**
* An external key was pressed (e.g., a volume key).
*
- * This event is used to estimate the key-up event based on by running a timer as a waiting
- * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp
- * event. Therefore, [onArrowUp] must be called after the timeout.
+ * This event is used to estimate the key-up event based on a running a timer as a waiting
+ * coroutine in the [pluginScope]. A key-up event in a slider corresponds to an onArrowUp event.
+ * Therefore, [onArrowUp] must be called after the timeout.
*/
fun onKeyDown() {
if (!isTracking) return
@@ -159,7 +156,7 @@
keyUpJob?.cancel()
}
keyUpJob =
- applicationScope.launch {
+ pluginScope?.launch {
delay(KEY_UP_TIMEOUT)
onArrowUp()
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index 10098fa..0af3038 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -17,9 +17,7 @@
package com.android.systemui.haptics.slider
import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
import kotlin.math.abs
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -31,21 +29,20 @@
*
* The tracker runs a state machine to execute actions on touch-based events typical of a seekable
* slider such as [android.widget.SeekBar]. Coroutines responsible for running the state machine,
- * collecting slider events and maintaining waiting states are run on the main thread via the
- * [com.android.systemui.dagger.qualifiers.Main] coroutine dispatcher.
+ * collecting slider events and maintaining waiting states are run on the provided [CoroutineScope].
*
* @param[sliderStateListener] Listener of the slider state.
* @param[sliderEventProducer] Producer of slider events arising from the slider.
- * @param[mainDispatcher] [CoroutineDispatcher] used to launch coroutines for the collection of
- * slider events and the launch of timer jobs.
+ * @param[trackerScope] [CoroutineScope] used to launch coroutines for the collection of slider
+ * events and the launch of timer jobs.
* @property[config] Configuration parameters of the slider tracker.
*/
class SeekableSliderTracker(
sliderStateListener: SliderStateListener,
sliderEventProducer: SliderEventProducer,
- @Main mainDispatcher: CoroutineDispatcher,
+ trackerScope: CoroutineScope,
private val config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
-) : SliderTracker(CoroutineScope(mainDispatcher), sliderStateListener, sliderEventProducer) {
+) : SliderTracker(trackerScope, sliderStateListener, sliderEventProducer) {
// History of the latest progress collected from slider events
private var latestProgress = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 4cabd70..2e233d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -327,7 +327,7 @@
@Override
public void onCreate() {
- ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+ ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();
if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4766a84..0ee924d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -130,8 +130,8 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
@@ -689,17 +689,18 @@
} else {
resetStateLocked();
}
- }
- if (simState == TelephonyManager.SIM_STATE_ABSENT) {
- // MVNO SIMs can become transiently NOT_READY when switching networks,
- // so we should only lock when they are ABSENT.
- if (lastSimStateWasLocked) {
- if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
- + "previous state was locked. Reset the state.");
+ } else {
+ if (lastSimStateWasLocked && mShowing) {
+ if (DEBUG_SIM_STATES) {
+ Log.d(TAG, "SIM moved to "
+ + "NOT_READY/ABSENT/UNKNOWN when the previous state "
+ + "was locked. Reset the state.");
+ }
resetStateLocked();
}
- mSimWasLocked.append(slotId, false);
}
+
+ mSimWasLocked.append(slotId, false);
}
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
@@ -956,16 +957,17 @@
* Animation launch controller for activities that occlude the keyguard.
*/
@VisibleForTesting
- final ActivityLaunchAnimator.Controller mOccludeAnimationController =
- new ActivityLaunchAnimator.Controller() {
+ final ActivityTransitionAnimator.Controller mOccludeAnimationController =
+ new ActivityTransitionAnimator.Controller() {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+ public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
}
@Override
- public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) {
+ public void onTransitionAnimationCancelled(
+ @Nullable Boolean newKeyguardOccludedState) {
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
mOccludeAnimationPlaying = false;
@@ -976,7 +978,7 @@
}
@Override
- public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+ public void onTransitionAnimationEnd(boolean launchIsFullScreen) {
if (launchIsFullScreen) {
mShadeController.get().instantCollapseShade();
}
@@ -993,23 +995,23 @@
@NonNull
@Override
- public ViewGroup getLaunchContainer() {
+ public ViewGroup getTransitionContainer() {
return ((ViewGroup) mKeyguardViewControllerLazy.get()
.getViewRootImpl().getView());
}
@Override
- public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
+ public void setTransitionContainer(@NonNull ViewGroup transitionContainer) {
// No-op, launch container is always the shade.
Log.wtf(TAG, "Someone tried to change the launch container for the "
- + "ActivityLaunchAnimator, which should never happen.");
+ + "ActivityTransitionAnimator, which should never happen.");
}
@NonNull
@Override
- public LaunchAnimator.State createAnimatorState() {
- final int fullWidth = getLaunchContainer().getWidth();
- final int fullHeight = getLaunchContainer().getHeight();
+ public TransitionAnimator.State createAnimatorState() {
+ final int fullWidth = getTransitionContainer().getWidth();
+ final int fullHeight = getTransitionContainer().getHeight();
if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
final float initialHeight = fullHeight / 3f;
@@ -1017,7 +1019,7 @@
// Start the animation near the power button, at one-third size, since the
// camera was launched from the power button.
- return new LaunchAnimator.State(
+ return new TransitionAnimator.State(
(int) (mPowerButtonY - initialHeight / 2f) /* top */,
(int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
(int) (fullWidth - initialWidth) /* left */,
@@ -1029,7 +1031,7 @@
// Start the animation in the center of the screen, scaled down to half
// size.
- return new LaunchAnimator.State(
+ return new TransitionAnimator.State(
(int) (fullHeight - initialHeight) / 2,
(int) (initialHeight + (fullHeight - initialHeight) / 2),
(int) (fullWidth - initialWidth) / 2,
@@ -1166,8 +1168,8 @@
/**
* Animation controller for activities that unocclude the keyguard. This does not use the
- * ActivityLaunchAnimator since we're just translating down, rather than emerging from a view
- * or the power button.
+ * ActivityTransitionAnimator since we're just translating down, rather than emerging from a
+ * view or the power button.
*/
private final IRemoteAnimationRunner mUnoccludeAnimationRunner =
new IRemoteAnimationRunner.Stub() {
@@ -1346,7 +1348,7 @@
private boolean mWallpaperSupportsAmbientMode;
private final KeyguardTransitions mKeyguardTransitions;
- private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private final Lazy<ActivityTransitionAnimator> mActivityTransitionAnimator;
private final Lazy<ScrimController> mScrimControllerLazy;
private final IActivityTaskManager mActivityTaskManagerService;
@@ -1393,7 +1395,7 @@
WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
FeatureFlags featureFlags,
@@ -1458,7 +1460,7 @@
mJavaAdapter = javaAdapter;
mWallpaperRepository = wallpaperRepository;
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityTransitionAnimator = activityTransitionAnimator;
mScrimControllerLazy = scrimControllerLazy;
mActivityTaskManagerService = activityTaskManagerService;
@@ -3811,15 +3813,15 @@
/**
* Implementation of RemoteAnimationRunner that creates a new
- * {@link ActivityLaunchAnimator.Runner} whenever onAnimationStart is called, delegating the
+ * {@link ActivityTransitionAnimator.Runner} whenever onAnimationStart is called, delegating the
* remote animation methods to that runner.
*/
private class ActivityLaunchRemoteAnimationRunner extends IRemoteAnimationRunner.Stub {
- private final ActivityLaunchAnimator.Controller mActivityLaunchController;
- @Nullable private ActivityLaunchAnimator.Runner mRunner;
+ private final ActivityTransitionAnimator.Controller mActivityLaunchController;
+ @Nullable private ActivityTransitionAnimator.Runner mRunner;
- ActivityLaunchRemoteAnimationRunner(ActivityLaunchAnimator.Controller controller) {
+ ActivityLaunchRemoteAnimationRunner(ActivityTransitionAnimator.Controller controller) {
mActivityLaunchController = controller;
}
@@ -3836,7 +3838,7 @@
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback)
throws RemoteException {
- mRunner = mActivityLaunchAnimator.get().createRunner(mActivityLaunchController);
+ mRunner = mActivityTransitionAnimator.get().createRunner(mActivityLaunchController);
mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
}
}
@@ -3849,7 +3851,7 @@
extends ActivityLaunchRemoteAnimationRunner {
OccludeActivityLaunchRemoteAnimationRunner(
- ActivityLaunchAnimator.Controller controller) {
+ ActivityTransitionAnimator.Controller controller) {
super(controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 70da3e7..0b227fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -35,7 +35,7 @@
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
@@ -150,7 +150,7 @@
WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
FeatureFlags featureFlags,
@@ -196,7 +196,7 @@
wallpaperRepository,
shadeController,
notificationShadeWindowController,
- activityLaunchAnimator,
+ activityTransitionAnimator,
scrimControllerLazy,
activityTaskManagerService,
featureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 5e3779a..59288a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -60,6 +60,8 @@
val currentClock: StateFlow<ClockController?>
+ val previewClock: StateFlow<ClockController>
+
val clockEventController: ClockEventController
fun setClockSize(@ClockSize size: Int)
}
@@ -120,6 +122,15 @@
initialValue = clockRegistry.createCurrentClock()
)
+ override val previewClock: StateFlow<ClockController> =
+ currentClockId
+ .map { clockRegistry.createCurrentClock() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = clockRegistry.createCurrentClock()
+ )
+
@VisibleForTesting
suspend fun getClockSize(): SettingsClockSize {
return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index cc1cf91..7ae70a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -74,6 +74,7 @@
BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Lazily, BurnInModel())
/**
* Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a97c152..0cf74a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,15 +24,15 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample as sampleUtil
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
@SysUISingleton
@@ -62,14 +62,17 @@
listenForTransitionToCamera(scope, keyguardInteractor)
}
+ @FlowPreview
private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
// Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
// will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
// happening prematurely.
- .onEach { delay(50) }
- .sample(
+ // This should eventually be removed in favor of
+ // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
+ .sample(150L)
+ .sampleCombine(
keyguardInteractor.primaryBouncerShowing,
startedKeyguardTransitionStep,
powerInteractor.isAwake,
@@ -111,19 +114,20 @@
private fun listenForAlternateBouncerToGone() {
scope.launch {
- keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
- (isKeyguardGoingAway, keyguardState) ->
- if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- startTransitionTo(KeyguardState.GONE)
+ keyguardInteractor.isKeyguardGoingAway
+ .sampleUtil(finishedKeyguardState, ::Pair)
+ .collect { (isKeyguardGoingAway, keyguardState) ->
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+ startTransitionTo(KeyguardState.GONE)
+ }
}
- }
}
}
private fun listenForAlternateBouncerToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
+ .sampleUtil(startedKeyguardTransitionStep, ::Pair)
.collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
if (
isPrimaryBouncerShowing &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 5606d43..e0b5c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -98,6 +98,8 @@
val modeOnCanceled =
if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
+ } else if (lastStartedStep.from == KeyguardState.GONE) {
+ TransitionModeOnCanceled.RESET
} else {
TransitionModeOnCanceled.LAST_VALUE
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 356c408..196770a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -44,6 +44,8 @@
val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
+ val previewClock: StateFlow<ClockController> = keyguardClockRepository.previewClock
+
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 00b7989..8b278cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -112,6 +112,38 @@
interpolator: Interpolator = LINEAR,
name: String? = null
): Flow<Float> {
+ return sharedFlowWithState(
+ duration = duration,
+ onStep = onStep,
+ startTime = startTime,
+ onStart = onStart,
+ onCancel = onCancel,
+ onFinish = onFinish,
+ interpolator = interpolator,
+ name = name,
+ )
+ .mapNotNull { stateToValue -> stateToValue.value }
+ }
+
+ /**
+ * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
+ * in the range of [0, 1]. View animations should begin and end within a subset of this
+ * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
+ * valid.
+ *
+ * Will return a [StateToValue], which encompasses the calculated value as well as the
+ * transitionState that is associated with it.
+ */
+ fun sharedFlowWithState(
+ duration: Duration,
+ onStep: (Float) -> Float,
+ startTime: Duration = 0.milliseconds,
+ onStart: (() -> Unit)? = null,
+ onCancel: (() -> Float)? = null,
+ onFinish: (() -> Float)? = null,
+ interpolator: Interpolator = LINEAR,
+ name: String? = null
+ ): Flow<StateToValue> {
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
@@ -164,7 +196,6 @@
.also { logger.logTransitionStep(name, step, it.value) }
}
.distinctUntilChanged()
- .mapNotNull { stateToValue -> stateToValue.value }
}
/**
@@ -174,9 +205,9 @@
return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
}
}
-
- data class StateToValue(
- val transitionState: TransitionState,
- val value: Float?,
- )
}
+
+data class StateToValue(
+ val transitionState: TransitionState = TransitionState.FINISHED,
+ val value: Float? = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 96e83b0..3630b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,7 +32,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.shared.model.Icon
@@ -534,7 +534,7 @@
activityStarter.postStartActivityDismissingKeyguard(
WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
/* delay= */ 0,
- /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+ /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view),
/* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f0e89f9..62a6e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -18,7 +18,6 @@
import android.transition.TransitionManager
import android.transition.TransitionSet
-import android.util.Log
import android.view.View.INVISIBLE
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
@@ -36,7 +35,6 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
@@ -78,10 +76,6 @@
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
- Log.d(
- "ClockViewBinder",
- "Sherry clockShouldBeCentered $clockShouldBeCentered"
- )
viewModel.clock?.let {
// Weather clock also has hasCustomPositionUpdatedAnimation as true
// TODO(b/323020908): remove ID check
@@ -169,16 +163,12 @@
rootView: ConstraintLayout,
) {
clockController?.let { clock ->
- clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
- if (clock.largeClock.layout.views.size == 1) {
- clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large
- }
- // small clock should either be a single view or container with id
- // `lockscreen_clock_view`
clock.smallClock.layout.views.forEach {
rootView.addView(it).apply { it.visibility = INVISIBLE }
}
- clock.largeClock.layout.views.forEach { rootView.addView(it) }
+ clock.largeClock.layout.views.forEach {
+ rootView.addView(it).apply { it.visibility = INVISIBLE }
+ }
}
}
fun applyConstraints(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 1b5b329..b56c998 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -17,12 +17,35 @@
package com.android.systemui.keyguard.ui.binder
+import android.content.Context
import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ClockEventController
+import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
+import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import com.android.systemui.util.Utils
+import kotlin.reflect.KSuspendFunction1
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/** Binder for the small clock view, large clock view. */
object KeyguardPreviewClockViewBinder {
@@ -45,4 +68,129 @@
}
}
}
+
+ @JvmStatic
+ fun bind(
+ context: Context,
+ rootView: ConstraintLayout,
+ viewModel: KeyguardPreviewClockViewModel,
+ clockEventController: ClockEventController,
+ updateClockAppearance: KSuspendFunction1<ClockController, Unit>
+ ) {
+ rootView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(viewModel.selectedClockSize, viewModel.previewClock) { _, clock ->
+ clock
+ }
+ .collect { previewClock ->
+ viewModel.lastClock?.let { lastClock ->
+ (lastClock.largeClock.layout.views +
+ lastClock.smallClock.layout.views)
+ .forEach { rootView.removeView(it) }
+ }
+ viewModel.lastClock = previewClock
+ clockEventController.clock = previewClock
+ updateClockAppearance(previewClock)
+
+ if (viewModel.shouldHighlightSelectedAffordance) {
+ (previewClock.largeClock.layout.views +
+ previewClock.smallClock.layout.views)
+ .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
+ }
+ previewClock.largeClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
+
+ previewClock.smallClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
+ applyPreviewConstraints(context, rootView, viewModel)
+ }
+ }
+ }
+ }
+ }
+
+ private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
+ constraints.apply {
+ constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+ val largeClockTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ context.resources.getDimensionPixelSize(
+ customizationR.dimen.small_clock_padding_top
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_smartspace_top_offset
+ ) +
+ getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
+ getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
+ connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+ connect(
+ R.id.lockscreen_clock_view_large,
+ ConstraintSet.END,
+ PARENT_ID,
+ ConstraintSet.END
+ )
+
+ connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+ constrainHeight(
+ R.id.lockscreen_clock_view,
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+ )
+ connect(
+ R.id.lockscreen_clock_view,
+ START,
+ PARENT_ID,
+ START,
+ context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
+ val smallClockTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+ }
+ }
+
+ private fun applyPreviewConstraints(
+ context: Context,
+ rootView: ConstraintLayout,
+ viewModel: KeyguardPreviewClockViewModel
+ ) {
+ val cs = ConstraintSet().apply { clone(rootView) }
+ val clock = viewModel.previewClock.value
+ applyClockDefaultConstraints(context, cs)
+ clock.largeClock.layout.applyPreviewConstraints(cs)
+ clock.smallClock.layout.applyPreviewConstraints(cs)
+
+ // When selectedClockSize is the initial value, make both clocks invisible to avoid
+ // flickering
+ val largeClockVisibility =
+ when (viewModel.selectedClockSize.value) {
+ SettingsClockSize.DYNAMIC -> VISIBLE
+ SettingsClockSize.SMALL -> INVISIBLE
+ null -> INVISIBLE
+ }
+ val smallClockVisibility =
+ when (viewModel.selectedClockSize.value) {
+ SettingsClockSize.DYNAMIC -> INVISIBLE
+ SettingsClockSize.SMALL -> VISIBLE
+ null -> INVISIBLE
+ }
+
+ cs.apply {
+ setVisibility(clock.largeClock.layout.views, largeClockVisibility)
+ setVisibility(clock.smallClock.layout.views, smallClockVisibility)
+ }
+ cs.applyTo(rootView)
+ }
+
+ private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+ private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 789d30f..9e7c70d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -145,9 +145,7 @@
launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
- // Reset alpha only for the icons, as they currently have their
- // own animator
- childViews[aodNotificationIconContainerId]?.alpha = 0f
+ childViews[aodNotificationIconContainerId]?.visibility = visibility
}
}
@@ -313,6 +311,12 @@
}
}
+ if (KeyguardShadeMigrationNssl.isEnabled) {
+ burnInParams.update { current ->
+ current.copy(translationY = { childViews[burnInLayerId]?.translationY })
+ }
+ }
+
onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
view.addOnLayoutChangeListener(onLayoutChangeListener)
@@ -435,11 +439,17 @@
}
when {
!isVisible.isAnimating -> {
- alpha = 1f
if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
- visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
+ visibility =
+ if (isVisible.value) {
+ alpha = 1f
+ View.VISIBLE
+ } else {
+ alpha = 0f
+ View.INVISIBLE
+ }
}
newAodTransition() -> {
animateInIconTranslation()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index f67cb68..b1adef4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -22,7 +22,7 @@
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -115,7 +115,7 @@
activityStarter.postStartActivityDismissingKeyguard(
WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
/* delay= */ 0,
- /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+ /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view),
/* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a0c0095..c14917b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -46,6 +46,7 @@
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -197,6 +198,9 @@
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
}
+ if (migrateClocksToBlueprint()) {
+ clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
+ }
runBlocking(mainDispatcher) {
host =
SurfaceControlViewHost(
@@ -396,11 +400,21 @@
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
+ if (migrateClocksToBlueprint()) {
+ KeyguardPreviewClockViewBinder.bind(
+ context,
+ keyguardRootView,
+ clockViewModel,
+ clockController,
+ ::updateClockAppearance
+ )
+ } else {
+ KeyguardPreviewClockViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ clockViewModel,
+ )
+ }
}
setUpSmartspace(previewContext, rootView)
@@ -474,55 +488,61 @@
private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
val resources = parentView.resources
- largeClockHostView = FrameLayout(previewContext)
- largeClockHostView.layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- )
- parentView.addView(largeClockHostView)
- largeClockHostView.isInvisible = true
+ if (!migrateClocksToBlueprint()) {
+ largeClockHostView = FrameLayout(previewContext)
+ largeClockHostView.layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+ largeClockHostView.isInvisible = true
+ parentView.addView(largeClockHostView)
- smallClockHostView = FrameLayout(previewContext)
- val layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
+ smallClockHostView = FrameLayout(previewContext)
+ val layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_height
+ )
)
+ layoutParams.topMargin =
+ KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_padding_top
+ )
+ smallClockHostView.layoutParams = layoutParams
+ smallClockHostView.setPaddingRelative(
+ /* start = */ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.clock_padding_start
+ ),
+ /* top = */ 0,
+ /* end = */ 0,
+ /* bottom = */ 0
)
- layoutParams.topMargin =
- KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
- )
- smallClockHostView.layoutParams = layoutParams
- smallClockHostView.setPaddingRelative(
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.clock_padding_start
- ),
- 0,
- 0,
- 0
- )
- smallClockHostView.clipChildren = false
- parentView.addView(smallClockHostView)
- smallClockHostView.isInvisible = true
+ smallClockHostView.clipChildren = false
+ parentView.addView(smallClockHostView)
+ smallClockHostView.isInvisible = true
+ }
// TODO (b/283465254): Move the listeners to KeyguardClockRepository
- val clockChangeListener =
- object : ClockRegistry.ClockChangeListener {
- override fun onCurrentClockChanged() {
- onClockChanged()
+ if (!migrateClocksToBlueprint()) {
+ val clockChangeListener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() {
+ onClockChanged()
+ }
}
- }
- clockRegistry.registerClockChangeListener(clockChangeListener)
- disposables.add(
- DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
- )
+ clockRegistry.registerClockChangeListener(clockChangeListener)
+ disposables.add(
+ DisposableHandle {
+ clockRegistry.unregisterClockChangeListener(clockChangeListener)
+ }
+ )
- clockController.registerListeners(parentView)
- disposables.add(DisposableHandle { clockController.unregisterListeners() })
+ clockController.registerListeners(parentView)
+ disposables.add(DisposableHandle { clockController.unregisterListeners() })
+ }
val receiver =
object : BroadcastReceiver() {
@@ -542,50 +562,61 @@
)
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- if (clockController.clock !is DefaultClockController) {
- clockController.clock
- ?.largeClock
- ?.events
- ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
- clockController.clock
- ?.smallClock
- ?.events
- ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView))
+ if (!migrateClocksToBlueprint()) {
+ val layoutChangeListener =
+ View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ if (clockController.clock !is DefaultClockController) {
+ clockController.clock
+ ?.largeClock
+ ?.events
+ ?.onTargetRegionChanged(
+ KeyguardClockSwitch.getLargeClockRegion(parentView)
+ )
+ clockController.clock
+ ?.smallClock
+ ?.events
+ ?.onTargetRegionChanged(
+ KeyguardClockSwitch.getSmallClockRegion(parentView)
+ )
+ }
}
- }
- parentView.addOnLayoutChangeListener(layoutChangeListener)
- disposables.add(
- DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
- )
+ parentView.addOnLayoutChangeListener(layoutChangeListener)
+ disposables.add(
+ DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
+ )
+ }
onClockChanged()
}
+ private suspend fun updateClockAppearance(clock: ClockController) {
+ clockController.clock = clock
+ val colors = wallpaperColors
+ if (clockRegistry.seedColor == null && colors != null) {
+ // Seed color null means users do not override any color on the clock. The default
+ // color will need to use wallpaper's extracted color and consider if the
+ // wallpaper's color is dark or light.
+ val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
+ val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+ val lightClockColor = wallpaperColorScheme.accent1.s100
+ val darkClockColor = wallpaperColorScheme.accent2.s600
+
+ // Note that when [wallpaperColors] is null, isWallpaperDark is true.
+ val isWallpaperDark: Boolean =
+ (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+ clock.events.onSeedColorChanged(
+ if (isWallpaperDark) lightClockColor else darkClockColor
+ )
+ }
+ }
private fun onClockChanged() {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
coroutineScope.launch {
val clock = clockRegistry.createCurrentClock()
clockController.clock = clock
-
- val colors = wallpaperColors
- if (clockRegistry.seedColor == null && colors != null) {
- // Seed color null means users do not override any color on the clock. The default
- // color will need to use wallpaper's extracted color and consider if the
- // wallpaper's color is dark or light.
- val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
- val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
- val lightClockColor = wallpaperColorScheme.accent1.s100
- val darkClockColor = wallpaperColorScheme.accent2.s600
-
- // Note that when [wallpaperColors] is null, isWallpaperDark is true.
- val isWallpaperDark: Boolean =
- (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
- clock.events.onSeedColorChanged(
- if (isWallpaperDark) lightClockColor else darkClockColor
- )
- }
-
+ updateClockAppearance(clock)
updateLargeClock(clock)
updateSmallClock(clock)
}
@@ -626,6 +657,9 @@
}
private fun updateLargeClock(clock: ClockController) {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
clock.largeClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
)
@@ -637,6 +671,9 @@
}
private fun updateSmallClock(clock: ClockController) {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
clock.smallClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
)
@@ -656,6 +693,6 @@
private const val KEY_DISPLAY_ID = "display_id"
private const val KEY_COLORS = "wallpaper_colors"
- private const val DIM_ALPHA = 0.3f
+ const val DIM_ALPHA = 0.3f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b1178f5..631b342 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -201,13 +201,16 @@
}
private fun getDimen(name: String): Int {
- val res = context.packageManager.getResourcesForApplication(context.packageName)
- val id = res.getIdentifier(name, "dimen", context.packageName)
- return res.getDimensionPixelSize(id)
+ return getDimen(context, name)
}
companion object {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+ fun getDimen(context: Context, name: String): Int {
+ val res = context.packageManager.getResourcesForApplication(context.packageName)
+ val id = res.getIdentifier(name, "dimen", context.packageName)
+ return res.getDimensionPixelSize(id)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 3737e6f..d26356e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -46,6 +47,14 @@
to = KeyguardState.GONE,
)
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ )
+
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index 9cf3c95..d4ea728 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -28,6 +28,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/** Models UI state for the alpha of the AOD (always-on display). */
@SysUISingleton
@@ -42,13 +43,15 @@
/** The alpha level for the entire lockscreen while in AOD. */
val alpha: Flow<Float> =
combine(
- keyguardTransitionInteractor.currentKeyguardState,
+ keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
+ emit(0f)
+ },
merge(
keyguardInteractor.keyguardAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
)
- ) { currentKeyguardState, alpha ->
- if (currentKeyguardState == KeyguardState.GONE) {
+ ) { transitionToGone, alpha ->
+ if (transitionToGone == 1f) {
// Ensures content is not visible when in GONE state
0f
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 780e323..8110de2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -28,6 +28,13 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import javax.inject.Inject
@@ -55,6 +62,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
private val keyguardClockViewModel: KeyguardClockViewModel,
) {
@@ -80,21 +88,22 @@
burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
goneToAodTransitionViewModel
.enterFromTopTranslationY(enterFromTopAmount)
- .onStart { emit(0f) },
+ .onStart { emit(StateToValue()) },
occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
emit(0f)
},
- ) {
- keyguardTransitionY,
- burnInTranslationY,
- goneToAodTransitionTranslationY,
- occludedToLockscreenTransitionTranslationY ->
-
- // All values need to be combined for a smooth translation
- keyguardTransitionY +
- burnInTranslationY +
- goneToAodTransitionTranslationY +
- occludedToLockscreenTransitionTranslationY
+ aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
+ emit(StateToValue())
+ },
+ ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
+ ->
+ if (isInTransition(aodToLockscreen.transitionState)) {
+ aodToLockscreen.value ?: 0f
+ } else if (isInTransition(goneToAod.transitionState)) {
+ (goneToAod.value ?: 0f) + burnInY
+ } else {
+ burnInY + occludedToLockscreen + keyguardTranslationY
+ }
}
}
.distinctUntilChanged()
@@ -112,12 +121,19 @@
}
}
+ private fun isInTransition(state: TransitionState): Boolean {
+ return state == STARTED || state == RUNNING
+ }
+
private fun burnIn(
params: BurnInParameters,
): Flow<BurnInModel> {
return combine(
merge(
- keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
+ keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
+ it.value
+ },
keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
)
.map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
@@ -179,6 +195,8 @@
val topInset: Int = 0,
/** Status view top, without translation added in */
val statusViewTop: Int = 0,
+ /** The current y translation of the view */
+ val translationY: () -> Float? = { null }
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index 266fd02..6d1d3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
+import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -48,6 +51,22 @@
to = KeyguardState.LOCKSCREEN,
)
+ /**
+ * Begin the transition from wherever the y-translation value is currently. This helps ensure a
+ * smooth transition if a transition in canceled.
+ */
+ fun translationY(currentTranslationY: () -> Float?): Flow<StateToValue> {
+ var startValue = 0f
+ return transitionAnimation.sharedFlowWithState(
+ duration = 500.milliseconds,
+ onStart = {
+ startValue = currentTranslationY() ?: 0f
+ startValue
+ },
+ onStep = { MathUtils.lerp(startValue, 0f, FAST_OUT_SLOW_IN.getInterpolation(it)) },
+ )
+ }
+
/** Ensure alpha is set to be visible */
val lockscreenAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index f5e6135..85885b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -48,8 +49,8 @@
)
/** y-translation from the top of the screen for AOD */
- fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
- return transitionAnimation.sharedFlow(
+ fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> {
+ return transitionAnimation.sharedFlowWithState(
startTime = 600.milliseconds,
duration = 500.milliseconds,
onStart = { translatePx },
@@ -63,8 +64,8 @@
/** alpha animation upon entering AOD */
val enterFromTopAnimationAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- startTime = 600.milliseconds,
- duration = 500.milliseconds,
+ startTime = 700.milliseconds,
+ duration = 400.milliseconds,
onStart = { 0f },
onStep = { it },
onFinish = { 1f },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index 5301302..f95a8a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -20,9 +20,14 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** View model for the small clock view, large clock view. */
class KeyguardPreviewClockViewModel
@@ -30,11 +35,24 @@
constructor(
@Application private val context: Context,
interactor: KeyguardClockInteractor,
+ @Application private val applicationScope: CoroutineScope,
) {
+ var shouldHighlightSelectedAffordance: Boolean = false
val isLargeClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
val isSmallClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+ var lastClock: ClockController? = null
+
+ val previewClock: StateFlow<ClockController> = interactor.previewClock
+
+ val selectedClockSize: StateFlow<SettingsClockSize?> =
+ interactor.selectedClockSize.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 709e184..ec13228 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -30,6 +30,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -48,6 +50,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -61,6 +64,9 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
@@ -75,6 +81,12 @@
val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
+ private val goneToAodTransitionRunning: Flow<Boolean> =
+ goneToAodTransition
+ .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ .onStart { emit(false) }
+ .distinctUntilChanged()
+
/** Last point that the root view was tapped */
val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
@@ -92,10 +104,15 @@
val alpha: Flow<Float> =
combine(
communalInteractor.isIdleOnCommunal,
+ // The transitions are mutually exclusive, so they are safe to merge to get the last
+ // value emitted by any of them. Do not add flows that cannot make this guarantee.
merge(
aodAlphaViewModel.alpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
)
) { isIdleOnCommunal, alpha ->
if (isIdleOnCommunal) {
@@ -130,6 +147,7 @@
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
combine(
+ goneToAodTransitionRunning,
keyguardTransitionInteractor.finishedKeyguardState.map {
KeyguardState.lockscreenVisibleInState(it)
},
@@ -137,6 +155,7 @@
areNotifsFullyHiddenAnimated(),
isPulseExpandingAnimated(),
) {
+ goneToAodTransitionRunning: Boolean,
onKeyguard: Boolean,
isBypassEnabled: Boolean,
notifsFullyHidden: AnimatedValue<Boolean>,
@@ -146,7 +165,9 @@
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
// animation is playing, in which case we want them to be visible if we're
// animating in the AOD UI and will be switching to KEYGUARD shortly.
- !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
+ goneToAodTransitionRunning ||
+ (!onKeyguard &&
+ !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
AnimatedValue.NotAnimating(false)
else ->
zip(notifsFullyHidden, pulseExpanding) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 2b28a71..fa18557 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -36,7 +36,7 @@
constructor(
@Application applicationScope: CoroutineScope,
deviceEntryInteractor: DeviceEntryInteractor,
- communalInteractor: CommunalInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
@@ -55,10 +55,12 @@
}
/** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: SceneKey? =
- if (communalInteractor.isCommunalEnabled) {
- SceneKey.Communal
- } else {
- null
- }
+ val leftDestinationSceneKey: StateFlow<SceneKey?> =
+ communalSettingsInteractor.isCommunalEnabled
+ .map { available -> if (available) SceneKey.Communal else null }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index a26ef07..d981650 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -46,12 +46,14 @@
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
+ duration = 200.milliseconds,
onStep = { 1 - it },
onFinish = { 0f },
onCancel = { 1f },
)
+ val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/OWNERS b/packages/SystemUI/src/com/android/systemui/media/OWNERS
index b2d00df..69ea57b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/media/OWNERS
@@ -1,5 +1 @@
per-file MediaProjectionPermissionActivity.java = michaelwr@google.com
-
-# Haptics team also works on Ringtone
-per-file NotificationPlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file RingtonePlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 7a48836..3ab0420 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -16,9 +16,6 @@
package com.android.systemui.media;
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
@@ -37,8 +34,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.vibrator.Flags;
import android.provider.MediaStore;
import android.util.Log;
@@ -58,7 +53,7 @@
@SysUISingleton
public class RingtonePlayer implements CoreStartable {
private static final String TAG = "RingtonePlayer";
- private static final boolean LOGD = true;
+ private static final boolean LOGD = false;
private final Context mContext;
// TODO: support Uri switching under same IBinder
@@ -91,11 +86,20 @@
*/
private class Client implements IBinder.DeathRecipient {
private final IBinder mToken;
- private Ringtone mRingtone;
+ private final Ringtone mRingtone;
- Client(@NonNull IBinder token, @NonNull Ringtone ringtone) {
- mToken = requireNonNull(token);
- mRingtone = requireNonNull(ringtone);
+ public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
+ this(token, uri, user, aa, null);
+ }
+
+ Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa,
+ @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+ mToken = token;
+
+ mRingtone = new Ringtone(getContextForUser(user), false);
+ mRingtone.setAudioAttributesField(aa);
+ mRingtone.setUri(uri, volumeShaperConfig);
+ mRingtone.createLocalMediaPlayer();
}
@Override
@@ -112,48 +116,24 @@
@Override
public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
throws RemoteException {
- if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
- playRemoteRingtone(token, uri, aa, true, Ringtone.MEDIA_SOUND,
- null, volume, looping, /* hapticGenerator= */ false,
- null);
- } else {
- playWithVolumeShaping(token, uri, aa, volume, looping, null);
- }
+ playWithVolumeShaping(token, uri, aa, volume, looping, null);
}
-
@Override
- public void playWithVolumeShaping(
- IBinder token, Uri uri, AudioAttributes aa, float volume,
+ public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
- Log.d(TAG, "playWithVolumeShaping(token=" + token + ", uri=" + uri + ", uid="
+ Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ Binder.getCallingUid() + ")");
}
Client client;
synchronized (mClients) {
client = mClients.get(token);
- }
- // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
- // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
- // waste the build.
- if (client == null) {
- final UserHandle user = Binder.getCallingUserHandle();
- Ringtone ringtone = Ringtone.createV1WithCustomAudioAttributes(
- getContextForUser(user), aa, uri, volumeShaperConfig,
- /* allowRemote= */ false);
- synchronized (mClients) {
- client = mClients.get(token);
- if (client == null) {
- client = new Client(token, ringtone);
- token.linkToDeath(client, 0);
- mClients.put(token, client);
- ringtone = null; // "owned" by the client now.
- }
- }
- // Clean up ringtone if it was abandoned (a client already existed).
- if (ringtone != null) {
- ringtone.stop();
+ if (client == null) {
+ final UserHandle user = Binder.getCallingUserHandle();
+ client = new Client(token, uri, user, aa, volumeShaperConfig);
+ token.linkToDeath(client, 0);
+ mClients.put(token, client);
}
}
client.mRingtone.setLooping(looping);
@@ -162,54 +142,6 @@
}
@Override
- public void playRemoteRingtone(IBinder token, Uri uri, AudioAttributes aa,
- boolean useExactAudioAttributes,
- @Ringtone.RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
- float volume,
- boolean looping, boolean isHapticGeneratorEnabled,
- @Nullable VolumeShaper.Configuration volumeShaperConfig)
- throws RemoteException {
- if (LOGD) {
- Log.d(TAG, "playRemoteRingtone(token=" + token + ", uri=" + uri + ", uid="
- + Binder.getCallingUid() + ")");
- }
-
- // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
- // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
- // waste the build.
- Client client;
- synchronized (mClients) {
- client = mClients.get(token);
- }
- if (client == null) {
- final UserHandle user = Binder.getCallingUserHandle();
- Ringtone ringtone = new Ringtone.Builder(getContextForUser(user), enabledMedia, aa)
- .setLocalOnly()
- .setUri(uri)
- .setLooping(looping)
- .setInitialSoundVolume(volume)
- .setUseExactAudioAttributes(useExactAudioAttributes)
- .setEnableHapticGenerator(isHapticGeneratorEnabled)
- .setVibrationEffect(vibrationEffect)
- .setVolumeShaperConfig(volumeShaperConfig)
- .build();
- if (ringtone == null) {
- return;
- }
- synchronized (mClients) {
- client = mClients.get(token);
- if (client == null) {
- client = new Client(token, ringtone);
- token.linkToDeath(client, 0);
- mClients.put(token, client);
- }
- }
- }
- // Ensure the client is initialized outside the all-clients lock, as it can be slow.
- client.mRingtone.play();
- }
-
- @Override
public void stop(IBinder token) {
if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
Client client;
@@ -235,10 +167,10 @@
return false;
}
}
+
@Override
public void setPlaybackProperties(IBinder token, float volume, boolean looping,
- boolean hapticGeneratorEnabled) {
- // RingtoneV1-exclusive path.
+ boolean hapticGeneratorEnabled) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -252,39 +184,6 @@
}
@Override
- public void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled) {
- Client client;
- synchronized (mClients) {
- client = mClients.get(token);
- }
- if (client != null) {
- client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
- }
- }
-
- @Override
- public void setLooping(IBinder token, boolean looping) {
- Client client;
- synchronized (mClients) {
- client = mClients.get(token);
- }
- if (client != null) {
- client.mRingtone.setLooping(looping);
- }
- }
-
- @Override
- public void setVolume(IBinder token, float volume) {
- Client client;
- synchronized (mClients) {
- client = mClients.get(token);
- }
- if (client != null) {
- client.mRingtone.setVolume(volume);
- }
- }
-
- @Override
public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa,
float volume) {
if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 3f4f347..8d918e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -22,9 +22,12 @@
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.res.R
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
+
+private const val TAG = "SeekBarObserver"
/**
* Observer for changes from SeekBarViewModel.
@@ -39,6 +42,10 @@
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
}
+ // Trace state loggers for playing and listening states of progress bar.
+ private val playingStateLogger = TraceStateLogger("$TAG#playing")
+ private val listeningStateLogger = TraceStateLogger("$TAG#listening")
+
val seekBarEnabledMaxHeight =
holder.seekBar.context.resources.getDimensionPixelSize(
R.dimen.qs_media_enabled_seekbar_height
@@ -103,9 +110,13 @@
return
}
+ playingStateLogger.log("${data.playing}")
+ listeningStateLogger.log("${data.listening}")
+
holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
holder.seekBar.isEnabled = data.seekAvailable
- progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
+ progressDrawable?.animate =
+ data.playing && !data.scrubbing && animationEnabled && data.listening
progressDrawable?.transitionEnabled = !data.seekAvailable
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index a91917a..40a9b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -84,7 +84,16 @@
@Background private val bgExecutor: RepeatableExecutor,
private val falsingManager: FalsingManager,
) {
- private var _data = Progress(false, false, false, false, null, 0)
+ private var _data =
+ Progress(
+ enabled = false,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = null,
+ duration = 0,
+ listening = false
+ )
set(value) {
val enabledChanged = value.enabled != field.enabled
field = value
@@ -239,7 +248,7 @@
)
false
else true
- _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
+ _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
checkIfPollingNeeded()
}
@@ -258,6 +267,7 @@
scrubbing = false,
elapsedTime = position,
duration = 100,
+ listening = false,
)
}
@@ -548,9 +558,12 @@
data class Progress(
val enabled: Boolean,
val seekAvailable: Boolean,
+ /** whether playback state is not paused or connecting */
val playing: Boolean,
val scrubbing: Boolean,
val elapsedTime: Int?,
- val duration: Int
+ val duration: Int,
+ /** whether seekBar is listening to progress updates */
+ val listening: Boolean,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 02f0d12..038582c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -26,14 +26,15 @@
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
+import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.animation.PhysicsAnimator
@@ -42,6 +43,7 @@
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
+private const val TAG = "MediaCarouselScrollHandler"
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio were not
@@ -63,6 +65,9 @@
private val logSmartspaceImpression: (Boolean) -> Unit,
private val logger: MediaUiEventLogger
) {
+ /** Trace state logger for media carousel visibility */
+ private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
+
/** Is the view in RTL */
val isRtl: Boolean
get() = scrollView.isLayoutRtl
@@ -182,6 +187,7 @@
if (field != value) {
field = value
seekBarUpdateListener.invoke(field)
+ visibleStateLogger.log("$visibleToUser")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 5720cc7..eae7789 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -81,7 +81,7 @@
import com.android.internal.widget.CachingIconView;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
@@ -1309,7 +1309,7 @@
}
@Nullable
- private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
+ private ActivityTransitionAnimator.Controller buildLaunchAnimatorController(
TransitionLayout player) {
if (!(player.getParent() instanceof ViewGroup)) {
// TODO(b/192194319): Throw instead of just logging.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 523414c..35e0271 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1155,7 +1155,7 @@
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
// TODO(b/311234666): revisit logic once interactions between the hub and
// shade/keyguard state are finalized
- isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
+ isCommunalShowing -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 375a0ce..687f268 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -78,7 +78,7 @@
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.flags.FeatureFlags;
@@ -400,7 +400,7 @@
void tryToLaunchInAppRoutingIntent(String routeId, View view) {
ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName();
if (componentName != null) {
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA);
launchIntent.setComponent(componentName);
@@ -412,7 +412,7 @@
}
void tryToLaunchMediaApplication(View view) {
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
Intent launchIntent = getAppLaunchIntent();
if (launchIntent != null) {
@@ -881,7 +881,7 @@
}
void launchBluetoothPairing(View view) {
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
if (controller == null || (mKeyGuardManager != null
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
index 2ae3a63..e475647 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
@@ -16,13 +16,18 @@
package com.android.systemui.media.muteawait
-import android.content.Context
+import android.annotation.SuppressLint
import android.media.AudioAttributes.USAGE_MEDIA
import android.media.AudioDeviceAttributes
import android.media.AudioManager
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import java.io.PrintWriter
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -30,14 +35,15 @@
/** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */
@SysUISingleton
class MediaMuteAwaitConnectionCli @Inject constructor(
- commandRegistry: CommandRegistry,
- private val context: Context
-) {
- init {
+ private val commandRegistry: CommandRegistry,
+ private val audioManager: AudioManager,
+) : CoreStartable {
+ override fun start() {
commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() }
}
inner class MuteAwaitCommand : Command {
+ @SuppressLint("MissingPermission")
override fun execute(pw: PrintWriter, args: List<String>) {
val device = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
@@ -49,8 +55,6 @@
)
val startOrCancel = args[2]
- val audioManager: AudioManager =
- context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
when (startOrCancel) {
START ->
audioManager.muteAwaitConnection(
@@ -65,6 +69,14 @@
"[type] [name] [$START|$CANCEL]")
}
}
+
+ @Module
+ interface StartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(MediaMuteAwaitConnectionCli::class)
+ fun bindsMediaMuteAwaitConnectionCli(impl: MediaMuteAwaitConnectionCli): CoreStartable
+ }
}
private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await"
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
index 64b772b..0dc10f6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
@@ -18,9 +18,14 @@
import android.media.INearbyMediaDevicesProvider
import android.media.INearbyMediaDevicesUpdateCallback
-import com.android.systemui.dagger.SysUISingleton
import android.os.IBinder
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.CommandQueue
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import javax.inject.Inject
/**
@@ -30,9 +35,9 @@
*/
@SysUISingleton
class NearbyMediaDevicesManager @Inject constructor(
- commandQueue: CommandQueue,
+ private val commandQueue: CommandQueue,
private val logger: NearbyMediaDevicesLogger
-) {
+) : CoreStartable {
private var providers: MutableList<INearbyMediaDevicesProvider> = mutableListOf()
private var activeCallbacks: MutableList<INearbyMediaDevicesUpdateCallback> = mutableListOf()
@@ -69,7 +74,7 @@
}
}
- init {
+ override fun start() {
commandQueue.addCallback(commandQueueCallbacks)
}
@@ -108,4 +113,12 @@
}
}
}
+
+ @Module
+ interface StartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(NearbyMediaDevicesManager::class)
+ fun bindsNearbyMediaDevicesManager(impl: NearbyMediaDevicesManager): CoreStartable
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 0167287..aa03e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -395,7 +395,7 @@
}
if (displayId == mDisplayId) {
mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
- BarTransitions.MODE_TRANSPARENT, navbarColorManagedByIme);
+ mTransitionMode, navbarColorManagedByIme);
}
if (mBehavior != behavior) {
mBehavior = behavior;
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index 2751072..3671dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,9 @@
package com.android.systemui.process;
+import android.os.Process;
+import android.os.UserHandle;
+
import javax.inject.Inject;
/**
@@ -30,6 +33,15 @@
* Returns {@code true} if System User is running the current process.
*/
public boolean isSystemUser() {
- return android.os.Process.myUserHandle().isSystem();
+ return myUserHandle().isSystem();
+ }
+
+ /**
+ * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
+ *
+ * Please strongly consider using {@link com.android.systemui.settings.UserTracker} instead.
+ */
+ public UserHandle myUserHandle() {
+ return Process.myUserHandle();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 0941a20..3fd9803 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -11,7 +11,7 @@
import androidx.annotation.WorkerThread
import com.android.internal.R
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.appops.AppOpsController
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
@@ -175,7 +175,7 @@
startSafetyCenter.flags = Intent.FLAG_ACTIVITY_NEW_TASK
uiExecutor.execute {
activityStarter.startActivity(startSafetyCenter, true,
- ActivityLaunchAnimator.Controller.fromView(privacyChip))
+ ActivityTransitionAnimator.Controller.fromView(privacyChip))
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index a2dfc01..c0d9644 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -157,6 +157,12 @@
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
parentHeightMeasureSpec, heightUsed);
}
+ } else {
+ // Don't measure the customizer with all the children, it will be measured separately
+ if (child != mQSCustomizer) {
+ super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
+ parentHeightMeasureSpec, heightUsed);
+ }
}
}
@@ -248,6 +254,25 @@
+ mHeader.getHeight();
}
+ // These next two methods are used with Scene container to determine the size of QQS and QS .
+
+ /**
+ * Returns the size of the QQS container, regardless of the measured size of this view.
+ * @return size in pixels of QQS
+ */
+ public int getQqsHeight() {
+ return mHeader.getHeight();
+ }
+
+ /**
+ * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view
+ * @return size in pixels of QS (or QSCustomizer)
+ */
+ public int getQsHeight() {
+ return mQSCustomizer.isCustomizing() ? mQSCustomizer.getMeasuredHeight()
+ : mQSPanel.getMeasuredHeight();
+ }
+
public void setExpansion(float expansion) {
mQsExpansion = expansion;
if (mQSPanelContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 290821e..4d55714 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -984,6 +984,14 @@
return mListeningAndVisibilityLifecycleOwner;
}
+ public int getQQSHeight() {
+ return mContainer.getQqsHeight();
+ }
+
+ public int getQSHeight() {
+ return mContainer.getQsHeight();
+ }
+
@NeverCompile
@Override
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index bd13d06..b53c245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -51,7 +51,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -65,14 +65,14 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
-
import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
public class CustomTile extends QSTileImpl<State> implements TileChangeListener,
CustomTileInterface {
public static final String PREFIX = "custom(";
@@ -540,9 +540,9 @@
} else {
Log.i(TAG, "The activity is starting");
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mViewClicked == null ? null :
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
mViewClicked,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 529d684..35cac4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -57,7 +57,7 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -70,7 +70,6 @@
import com.android.systemui.qs.logging.QSLogger;
import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* Base quick-settings tile, extend this to create a new tile.
@@ -412,8 +411,8 @@
* @param view The view from which the opening window will be animated.
*/
protected void handleLongClick(@Nullable View view) {
- ActivityLaunchAnimator.Controller animationController =
- view != null ? ActivityLaunchAnimator.Controller.fromView(view,
+ ActivityTransitionAnimator.Controller animationController =
+ view != null ? ActivityTransitionAnimator.Controller.fromView(view,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null;
mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
animationController);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index d98141f..688f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -14,7 +14,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +75,7 @@
override fun handleClick(view: View?) {
val animationController = view?.let {
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
}
val pendingIntent = lastAlarmInfo?.showIntent
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a698208..690b711 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
@@ -222,7 +222,7 @@
mContext,
ROUTE_TYPE_REMOTE_DISPLAY,
v -> {
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(v);
if (controller == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 91b2d97..bb175e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -26,7 +26,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
@@ -112,7 +112,7 @@
putExtra(ControlsUiController.EXTRA_ANIMATE, true)
}
val animationController = view?.let {
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index f70e27d..de9a08e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -27,8 +27,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -40,6 +39,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 javax.inject.Inject;
@@ -108,8 +108,8 @@
return;
}
- ActivityLaunchAnimator.Controller animationController =
- view == null ? null : ActivityLaunchAnimator.Controller.fromView(view,
+ ActivityTransitionAnimator.Controller animationController =
+ view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mActivityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 3b8fb26..1b73225 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -43,7 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -132,8 +132,8 @@
@Override
protected void handleClick(@Nullable View view) {
- ActivityLaunchAnimator.Controller animationController =
- view == null ? null : ActivityLaunchAnimator.Controller.fromView(view,
+ ActivityTransitionAnimator.Controller animationController =
+ view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mUiHandler.post(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index fe10eaa..7192f58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -22,7 +22,7 @@
import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
@@ -53,9 +53,9 @@
) : QSTileIntentUserInputHandler {
override fun handle(view: View?, intent: Intent) {
- val animationController: ActivityLaunchAnimator.Controller? =
+ val animationController: ActivityTransitionAnimator.Controller? =
view?.let {
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
it,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
)
@@ -70,9 +70,9 @@
requestLaunchingDefaultActivity: Boolean
) {
if (pendingIntent.isActivity) {
- val animationController: ActivityLaunchAnimator.Controller? =
+ val animationController: ActivityTransitionAnimator.Controller? =
view?.let {
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
it,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 211b4594..41de65c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -75,7 +75,7 @@
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.settingslib.wifi.WifiUtils;
import com.android.settingslib.wifi.dpp.WifiDppIntentHelper;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -748,7 +748,7 @@
}
private void startActivity(Intent intent, View view) {
- ActivityLaunchAnimator.Controller controller =
+ ActivityTransitionAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
if (controller == null && mCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 1b3b584..58e7613 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -40,7 +40,7 @@
val icon =
Icon.Loaded(
resources.getDrawable(
- if (data.isEnabled) {
+ if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
R.drawable.qs_flashlight_icon_on
} else {
R.drawable.qs_flashlight_icon_off
@@ -51,17 +51,22 @@
)
this.icon = { icon }
- if (data.isEnabled) {
+ contentDescription = label
+
+ if (data is FlashlightTileModel.FlashlightTemporarilyUnavailable) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.quick_settings_flashlight_camera_in_use)
+ stateDescription = secondaryLabel
+ supportedActions = setOf()
+ return@build
+ } else if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
} else {
activationState = QSTileState.ActivationState.INACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
}
- contentDescription = label
- supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- )
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 53d4cf9..1544804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -38,25 +38,30 @@
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<FlashlightTileModel> = conflatedCallbackFlow {
- val initialValue = flashlightController.isEnabled
- trySend(FlashlightTileModel(initialValue))
-
val callback =
object : FlashlightController.FlashlightListener {
override fun onFlashlightChanged(enabled: Boolean) {
- trySend(FlashlightTileModel(enabled))
+ trySend(FlashlightTileModel.FlashlightAvailable(enabled))
}
override fun onFlashlightError() {
- trySend(FlashlightTileModel(false))
+ trySend(FlashlightTileModel.FlashlightAvailable(false))
}
override fun onFlashlightAvailabilityChanged(available: Boolean) {
- trySend(FlashlightTileModel(flashlightController.isEnabled))
+ trySend(
+ if (available)
+ FlashlightTileModel.FlashlightAvailable(flashlightController.isEnabled)
+ else FlashlightTileModel.FlashlightTemporarilyUnavailable
+ )
}
}
flashlightController.addCallback(callback)
awaitClose { flashlightController.removeCallback(callback) }
}
+ /**
+ * Used to determine if the tile should be displayed. Not to be confused with the availability
+ * in the data model above. This flow signals whether the tile should be shown or hidden.
+ */
override fun availability(user: UserHandle): Flow<Boolean> =
flowOf(flashlightController.hasFlashlight())
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
index 9180e30..bedd65e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -35,7 +35,10 @@
with(input) {
when (action) {
is QSTileUserAction.Click -> {
- if (!ActivityManager.isUserAMonkey()) {
+ if (
+ !ActivityManager.isUserAMonkey() &&
+ input.data is FlashlightTileModel.FlashlightAvailable
+ ) {
flashlightController.setFlashlight(!input.data.isEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index ef6b2be..f54b371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -16,9 +16,14 @@
package com.android.systemui.qs.tiles.impl.flashlight.domain.model
-/**
- * Flashlight tile model.
- *
- * @param isEnabled is true when the falshlight is enabled;
- */
-@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
+sealed interface FlashlightTileModel {
+ /**
+ * In this state, the tile can be turned on or off.
+ *
+ * @param isEnabled is true when the flashlight is on and false when off.
+ */
+ @JvmInline value class FlashlightAvailable(val isEnabled: Boolean) : FlashlightTileModel
+
+ /** In this state the tile is grayed out and flashlight cannot be turned on. */
+ data object FlashlightTemporarilyUnavailable : FlashlightTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 0d43396..3d12eed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -28,6 +28,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.QSContainerImpl
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
@@ -68,6 +69,15 @@
/** Set the current state for QS. [state]. */
fun setState(state: State)
+ /** The current height of QQS in the current [qsView], or 0 if there's no view. */
+ val qqsHeight: Int
+
+ /**
+ * The current height of QS in the current [qsView], or 0 if there's no view. If customizing, it
+ * will return the height allocated to the customizer.
+ */
+ val qsHeight: Int
+
sealed class State(
val isVisible: Boolean,
val expansion: Float,
@@ -121,6 +131,11 @@
val qsImpl = _qsImpl.asStateFlow()
override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
+ override val qqsHeight: Int
+ get() = qsImpl.value?.qqsHeight ?: 0
+ override val qsHeight: Int
+ get() = qsImpl.value?.qsHeight ?: 0
+
// Same config changes as in FragmentHostManager
private val interestingChanges =
InterestingConfigChanges(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index fcbe9a6..356eb85 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -45,8 +45,8 @@
sceneKeys =
listOf(
SceneKey.Gone,
- SceneKey.Shade,
SceneKey.QuickSettings,
+ SceneKey.Shade,
),
initialSceneKey = SceneKey.Gone,
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0f3acaf..c7d3a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -69,8 +69,8 @@
SceneKey.Communal,
SceneKey.Lockscreen,
SceneKey.Bouncer,
- SceneKey.Shade,
SceneKey.QuickSettings,
+ SceneKey.Shade,
),
initialSceneKey = SceneKey.Lockscreen,
)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
index 76d1d3d..6199a83 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index e9af295..861a2ed 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -31,8 +31,8 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.classifier.Classifier;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
+import com.android.systemui.haptics.slider.HapticSliderViewBinder;
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
@@ -42,8 +42,6 @@
import javax.inject.Inject;
-import kotlinx.coroutines.CoroutineDispatcher;
-
/**
* {@code ViewController} for a {@code BrightnessSliderView}
*
@@ -63,23 +61,16 @@
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
- private final BrightnessSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mBrightnessSliderHapticPlugin.onTouchEvent(ev);
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mFalsingManager.isFalseTouch(Classifier.BRIGHTNESS_SLIDER);
- if (mBrightnessSliderHapticPlugin.getVelocityTracker() != null) {
- mBrightnessSliderHapticPlugin.getVelocityTracker().clear();
- }
- } else if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
- if (mBrightnessSliderHapticPlugin.getVelocityTracker() != null) {
- mBrightnessSliderHapticPlugin.getVelocityTracker().addMovement(ev);
- }
}
-
return false;
}
@@ -93,7 +84,7 @@
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
- BrightnessSliderHapticPlugin brightnessSliderHapticPlugin) {
+ SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
super(brightnessSliderView);
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
@@ -112,7 +103,6 @@
protected void onViewAttached() {
mView.setOnSeekBarChangeListener(mSeekListener);
mView.setOnInterceptListener(mOnInterceptListener);
- mBrightnessSliderHapticPlugin.start();
}
@Override
@@ -120,7 +110,6 @@
mView.setOnSeekBarChangeListener(null);
mView.setOnDispatchTouchEventListener(null);
mView.setOnInterceptListener(null);
- mBrightnessSliderHapticPlugin.stop();
}
@Override
@@ -225,10 +214,8 @@
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mListener != null) {
mListener.onChanged(mTracking, progress, false);
- SeekableSliderEventProducer eventProducer =
- mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null && fromUser) {
- eventProducer.onProgressChanged(seekBar, progress, fromUser);
+ if (fromUser) {
+ mBrightnessSliderHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
}
}
}
@@ -239,11 +226,7 @@
mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), false);
- SeekableSliderEventProducer eventProducer =
- mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
- eventProducer.onStartTrackingTouch(seekBar);
- }
+ mBrightnessSliderHapticPlugin.onStartTrackingTouch(seekBar);
}
if (mMirrorController != null) {
@@ -258,11 +241,7 @@
mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), true);
- SeekableSliderEventProducer eventProducer =
- mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
- eventProducer.onStopTrackingTouch(seekBar);
- }
+ mBrightnessSliderHapticPlugin.onStopTrackingTouch(seekBar);
}
if (mMirrorController != null) {
@@ -280,21 +259,18 @@
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
- private final CoroutineDispatcher mMainDispatcher;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
- SystemClock clock,
- @Main CoroutineDispatcher mainDispatcher
+ SystemClock clock
) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
- mMainDispatcher = mainDispatcher;
}
/**
@@ -310,15 +286,11 @@
int layout = getLayout();
BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
.inflate(layout, viewRoot, false);
- BrightnessSliderHapticPlugin plugin;
- if (hapticBrightnessSlider()) {
- plugin = new BrightnessSliderHapticPluginImpl(
+ SeekableSliderHapticPlugin plugin = new SeekableSliderHapticPlugin(
mVibratorHelper,
- mSystemClock,
- mMainDispatcher
- );
- } else {
- plugin = new BrightnessSliderHapticPlugin() {};
+ mSystemClock);
+ if (hapticBrightnessSlider()) {
+ HapticSliderViewBinder.bind(viewRoot, plugin);
}
return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt
deleted file mode 100644
index f775114..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings.brightness
-
-import android.view.VelocityTracker
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-
-/** Plugin component for the System UI brightness slider to incorporate dynamic haptics */
-interface BrightnessSliderHapticPlugin {
-
- /** Finger velocity tracker */
- val velocityTracker: VelocityTracker?
- get() = null
-
- /** Producer of slider events from the underlying [android.widget.SeekBar] */
- val seekableSliderEventProducer: SeekableSliderEventProducer?
- get() = null
-
- /**
- * Start the plugin.
- *
- * This starts the tracking of slider states, events and triggering of haptic feedback.
- */
- fun start() {}
-
- /**
- * Stop the plugin
- *
- * This stops the tracking of slider states, events and triggers of haptic feedback.
- */
- fun stop() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt
deleted file mode 100644
index 32561f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings.brightness
-
-import android.view.VelocityTracker
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-import com.android.systemui.haptics.slider.SeekableSliderTracker
-import com.android.systemui.haptics.slider.SliderHapticFeedbackProvider
-import com.android.systemui.haptics.slider.SliderTracker
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineDispatcher
-
-/**
- * Implementation of the [BrightnessSliderHapticPlugin].
- *
- * For the specifics of the brightness slider in System UI, a [SeekableSliderEventProducer] is used
- * as the producer of slider events, a [SliderHapticFeedbackProvider] is used as the listener of
- * slider states to play haptic feedback depending on the state, and a [SeekableSliderTracker] is
- * used as the state machine handler that tracks and manipulates the slider state.
- */
-class BrightnessSliderHapticPluginImpl
-@JvmOverloads
-constructor(
- vibratorHelper: VibratorHelper,
- systemClock: SystemClock,
- @Main mainDispatcher: CoroutineDispatcher,
- override val velocityTracker: VelocityTracker = VelocityTracker.obtain(),
- override val seekableSliderEventProducer: SeekableSliderEventProducer =
- SeekableSliderEventProducer(),
-) : BrightnessSliderHapticPlugin {
-
- private val sliderHapticFeedbackProvider: SliderHapticFeedbackProvider =
- SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, clock = systemClock)
- private val sliderTracker: SliderTracker =
- SeekableSliderTracker(
- sliderHapticFeedbackProvider,
- seekableSliderEventProducer,
- mainDispatcher,
- )
-
- val isTracking: Boolean
- get() = sliderTracker.isTracking
-
- override fun start() {
- if (!sliderTracker.isTracking) {
- sliderTracker.startTracking()
- }
- }
-
- override fun stop() {
- sliderTracker.stopTracking()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 1c7cc00..df845f5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -35,6 +35,7 @@
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -105,13 +106,9 @@
*/
private var shadeShowing = false
- /** Returns true if the glanceable hub is enabled and the container view can be created. */
- fun isEnabled(): Boolean {
- return communalInteractor.isCommunalEnabled && isComposeAvailable()
- }
-
- /** Returns a {@link StateFlow} that tracks whether communal hub is available. */
- fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable
+ /** Returns a flow that tracks whether communal hub is available. */
+ fun communalAvailable(): Flow<Boolean> =
+ if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false)
/**
* Creates the container view containing the glanceable hub UI.
@@ -127,9 +124,6 @@
/** Override for testing. */
@VisibleForTesting
internal fun initView(containerView: View): View {
- if (!isEnabled()) {
- throw RuntimeException("Glanceable hub is not enabled")
- }
if (communalContainerView != null) {
throw RuntimeException("Communal view has already been initialized")
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e219bcc..353e143 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -112,8 +112,8 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -259,7 +259,7 @@
/** The parallax amount of the quick settings translation when dragging down the panel. */
public static final float QS_PARALLAX_AMOUNT = 0.175f;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
- ActivityLaunchAnimator.TIMINGS.getTotalDuration()
+ ActivityTransitionAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
private static final int NO_FIXED_DURATION = -1;
@@ -1021,22 +1021,24 @@
instantCollapse();
} else {
mView.animate().cancel();
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- // Translate up by 4%.
- .translationY(mView.getHeight() * -0.04f)
- // This start delay is to give us time to animate out before
- // the launcher icons animation starts, so use that as our
- // duration.
- .setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_ACCELERATE)
- .withEndAction(() -> {
- instantCollapse();
- mView.setAlpha(1f);
- mView.setTranslationY(0f);
- })
- .start();
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ // Translate up by 4%.
+ .translationY(mView.getHeight() * -0.04f)
+ // This start delay is to give us time to animate out before
+ // the launcher icons animation starts, so use that as our
+ // duration.
+ .setDuration(unlockAnimationStartDelay)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
+ .withEndAction(() -> {
+ instantCollapse();
+ mView.setAlpha(1f);
+ mView.setTranslationY(0f);
+ })
+ .start();
+ }
}
}
}
@@ -1346,7 +1348,7 @@
}
updateClockAppearance();
mQsController.updateQsState();
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) {
mNotificationStackScrollLayoutController.updateFooter();
}
}
@@ -3229,7 +3231,7 @@
@Override
public void applyLaunchAnimationProgress(float linearProgress) {
- boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+ boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.TIMINGS,
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
if (hideIcons != mHideIconsDuringLaunchAnimation) {
mHideIconsDuringLaunchAnimation = hideIcons;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index ef820f3..aa2d606 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -34,7 +34,7 @@
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.LockIconViewController;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.binder.BouncerViewBinder;
@@ -679,7 +679,7 @@
void setExpandAnimationRunning(boolean running) {
if (mExpandAnimationRunning != running) {
// TODO(b/288507023): Remove this log.
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "Setting mExpandAnimationRunning=" + running);
}
if (running) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 67bb814..f89a9c70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
import dagger.Binds
@@ -35,6 +36,10 @@
@Binds
@SysUISingleton
+ abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor
+
+ @Binds
+ @SysUISingleton
abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index fc2c3ee..e4d5d22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -24,6 +24,8 @@
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideShadeBackActionInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
+ sceneContainerOff: Provider<NotificationPanelViewController>
+ ): ShadeBackActionInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 4f970b3..0befb61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@
* this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
* pulled up into ShadeViewController.
*/
-interface ShadeSurface : ShadeViewController {
+interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
/** Initialize objects instead of injecting to avoid circular dependencies. */
fun initDependencies(
centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3430eed..74035bd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -79,15 +79,8 @@
/** Collapses the shade instantly without animation. */
fun instantCollapse()
- /**
- * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
- * in split shade, it will collapse the whole shade.
- *
- * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
- */
- fun animateCollapseQs(fullyCollapse: Boolean)
-
/** Returns whether the shade can be collapsed. */
+ @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
fun canBeCollapsed(): Boolean
/** Returns whether the shade is in the process of collapsing. */
@@ -142,22 +135,6 @@
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
- /**
- * Close the keyguard user switcher if it is open and capable of closing.
- *
- * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
- * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
- *
- * @return true if the keyguard user switcher was open, and is now closed
- */
- fun closeUserSwitcherIfOpen(): Boolean
-
- /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
- fun onBackPressed()
-
- /** Sets progress of the predictive back animation. */
- fun onBackProgressed(progressFraction: Float)
-
/** Sets the alpha value of the shade to a value between 0 and 255. */
fun setAlpha(alpha: Int, animate: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 1240c6e..5d966ac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,13 +19,15 @@
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
/** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+class ShadeViewControllerEmptyImpl @Inject constructor() :
+ ShadeViewController, ShadeBackActionInteractor {
override fun expand(animate: Boolean) {}
override fun expandToQs() {}
override fun expandToNotifications() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
new file mode 100644
index 0000000..15ea219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.shade.domain.interactor
+
+/** Allows the back action to interact with the shade. */
+interface ShadeBackActionInteractor {
+ /**
+ * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+ * in split shade, it will collapse the whole shade.
+ *
+ * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ fun animateCollapseQs(fullyCollapse: Boolean)
+
+ /** Returns whether the shade can be collapsed. */
+ fun canBeCollapsed(): Boolean
+
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+ * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ fun closeUserSwitcherIfOpen(): Boolean
+
+ /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ fun onBackPressed()
+
+ /** Sets progress of the predictive back animation. */
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ fun onBackProgressed(progressFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
new file mode 100644
index 0000000..9bbe1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Implementation of ShadeBackActionInteractor backed by scenes. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeBackActionInteractorImpl
+@Inject
+constructor(
+ val shadeInteractor: ShadeInteractor,
+ val sceneInteractor: SceneInteractor,
+ val deviceEntryInteractor: DeviceEntryInteractor,
+) : ShadeBackActionInteractor {
+ override fun animateCollapseQs(fullyCollapse: Boolean) {
+ if (shadeInteractor.isQsExpanded.value) {
+ val key =
+ if (fullyCollapse) {
+ if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ } else {
+ SceneKey.Shade
+ }
+ sceneInteractor.changeScene(SceneModel(key), "animateCollapseQs")
+ }
+ }
+
+ override fun canBeCollapsed(): Boolean {
+ return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
+ }
+
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+
+ override fun onBackPressed() {
+ animateCollapseQs(false)
+ }
+
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ override fun onBackProgressed(progressFraction: Float) {
+ // Not supported. Do nothing.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 6e85074..ac510fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -156,6 +156,54 @@
}
}
+data class LinearSideLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
+
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
+ scrim.interpolatedRevealAmount = amount
+ scrim.startColorAlpha =
+ getPercentPastThreshold(1 - amount, threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+ scrim.revealGradientEndColorAlpha =
+ 1f -
+ getPercentPastThreshold(
+ amount,
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ )
+
+ val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
+ if (isVertical) {
+ scrim.setRevealGradientBounds(
+ left = -(scrim.viewWidth) * gradientBoundsAmount,
+ top = -(scrim.viewHeight) * gradientBoundsAmount,
+ right = (scrim.viewWidth) * gradientBoundsAmount,
+ bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+ )
+ } else {
+ scrim.setRevealGradientBounds(
+ left = -(scrim.viewWidth) * gradientBoundsAmount,
+ top = -(scrim.viewHeight) * gradientBoundsAmount,
+ right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
+ bottom = (scrim.viewHeight) * gradientBoundsAmount
+ )
+ }
+ }
+
+ private companion object {
+ // From which percentage we should start the gradient reveal width
+ // E.g. if 0 - starts with 0px width, 0.6f - starts with 60% width
+ private const val GRADIENT_START_BOUNDS_PERCENTAGE: Float = 1f
+
+ // When to start changing alpha color of the gradient scrim
+ // E.g. if 0.6f - starts fading the gradient away at 60% and becomes completely
+ // transparent at 100%
+ private const val REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE: Float = 1f
+
+ // When to finish displaying start color fill that reveals the content
+ // E.g. if 0.6f - the content won't be visible at 0% and it will gradually
+ // reduce the alpha until 60% (at this point the color fill is invisible)
+ private const val START_COLOR_REVEAL_PERCENTAGE: Float = 1f
+ }
+}
+
data class CircleReveal(
/** X-value of the circle center of the reveal. */
val centerX: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index b64e0b7..91340be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,7 +26,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -190,8 +190,8 @@
/** */
@Provides
@SysUISingleton
- static ActivityLaunchAnimator provideActivityLaunchAnimator() {
- return new ActivityLaunchAnimator();
+ static ActivityTransitionAnimator provideActivityTransitionAnimator() {
+ return new ActivityTransitionAnimator();
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index 785e65d..4af8cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -2,9 +2,9 @@
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.app.animation.Interpolators
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
import kotlin.math.min
/** Parameters for the notifications launch expanding animations. */
@@ -16,7 +16,7 @@
topCornerRadius: Float = 0f,
bottomCornerRadius: Float = 0f
-) : LaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
+) : TransitionAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
@VisibleForTesting
constructor() : this(
top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
@@ -58,7 +58,11 @@
}
fun getProgress(delay: Long, duration: Long): Float {
- return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
- duration)
+ return TransitionAnimator.getProgress(
+ ActivityTransitionAnimator.TIMINGS,
+ linearProgress,
+ delay,
+ duration
+ )
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 5983fc1..02d1e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -19,8 +19,8 @@
import android.util.Log
import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -55,9 +55,9 @@
}
/**
- * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance
- * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a
- * notification expanding into an opening window.
+ * An [ActivityTransitionAnimator.Controller] that animates an [ExpandableNotificationRow]. An
+ * instance of this class can be passed to [ActivityTransitionAnimator.startIntentWithAnimation] to
+ * animate a notification expanding into an opening window.
*/
class NotificationLaunchAnimatorController(
private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
@@ -66,7 +66,7 @@
private val notification: ExpandableNotificationRow,
private val jankMonitor: InteractionJankMonitor,
private val onFinishAnimationCallback: Runnable?
-) : ActivityLaunchAnimator.Controller {
+) : ActivityTransitionAnimator.Controller {
companion object {
const val ANIMATION_DURATION_TOP_ROUNDING = 100L
@@ -75,13 +75,13 @@
private val notificationEntry = notification.entry
private val notificationKey = notificationEntry.sbn.key
- override var launchContainer: ViewGroup
+ override var transitionContainer: ViewGroup
get() = notification.rootView as ViewGroup
set(ignored) {
// Do nothing. Notifications are always animated inside their rootView.
}
- override fun createAnimatorState(): LaunchAnimator.State {
+ override fun createAnimatorState(): TransitionAnimator.State {
// If the notification panel is collapsed, the clip may be larger than the height.
val height = max(0, notification.actualHeight - notification.clipBottomAmount)
val location = notification.locationOnScreen
@@ -140,7 +140,7 @@
}
override fun onIntentStarted(willAnimate: Boolean) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
}
notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
@@ -173,8 +173,8 @@
headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate)
}
- override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "onLaunchAnimationCancelled()")
}
@@ -186,15 +186,15 @@
onFinishAnimationCallback?.run()
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
notification.isExpandAnimationRunning = true
notificationListContainer.setExpandingNotification(notification)
jankMonitor.begin(notification, InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "onLaunchAnimationEnd()")
}
jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
@@ -213,8 +213,8 @@
notificationListContainer.applyLaunchAnimationParams(params)
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 3f2c818..7c71864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -386,6 +386,7 @@
}
}
+ @Deprecated("As part of b/301915812")
private fun scheduleDelayedDozeAmountAnimation() {
val alreadyRunning = delayedDozeAmountAnimator != null
logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 6e2beb4..8b0b973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -56,8 +56,8 @@
if (FooterViewRefactor.isEnabled) {
activeNotificationsInteractor.setNotifStats(notifStats)
}
- // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
- // visibility is handled in the new stack.
+ // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the silent
+ // section clear action is handled in the new stack.
controller.setNotifStats(notifStats)
if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
renderListInteractor.setRenderedList(entries)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
index 22ce4f1..a3189a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.util.Log
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import javax.inject.Inject
@@ -40,7 +40,7 @@
/** Sets whether the notification expansion launch animation is currently running. */
fun setIsLaunchAnimationRunning(running: Boolean) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
}
repository.isLaunchAnimationRunning.value = running
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
index 0a9e12a..ccf6f40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 5111c11..b23ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -52,6 +52,9 @@
isVisible =
activeNotificationsInteractor.hasClearableNotifications
.sample(
+ // TODO(b/322167853): This check is currently duplicated in
+ // NotificationListViewModel, but instead it should be a field in
+ // ShadeAnimationInteractor.
combine(
shadeInteractor.isShadeFullyExpanded,
shadeInteractor.isShadeTouchable,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index d903f06..8768ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -29,6 +29,8 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -56,6 +58,8 @@
panelTouchesEnabled && isKeyguardVisible
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** Amount of a "white" tint to be applied to the icons. */
val tintAlpha: Flow<Float> =
@@ -70,6 +74,8 @@
aodAmt + dozeAmt // If transitioning between them, they should sum to 1f
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** Are notification icons animated (ex: animated gif)? */
val areIconAnimationsEnabled: Flow<Boolean> =
@@ -78,8 +84,10 @@
// Don't animate icons when we're on AOD / dozing
it != KeyguardState.AOD && it != KeyguardState.DOZING
}
- .flowOn(bgContext)
.onStart { emit(true) }
+ .flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** [NotificationIconsViewData] indicating which icons to display in the view. */
val icons: Flow<NotificationIconsViewData> =
@@ -91,4 +99,6 @@
)
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 3574828..260cccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -21,6 +21,8 @@
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -56,4 +58,6 @@
)
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 38921c2..a64f888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -35,6 +35,7 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
@@ -64,10 +65,13 @@
panelTouchesEnabled && !isKeyguardShowing
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** The colors with which to display the notification icons. */
val iconColors: Flow<NotificationIconColorLookup> =
- combine(darkIconInteractor.tintAreas, darkIconInteractor.tintColor) { areas, tint ->
+ darkIconInteractor.darkState
+ .map { (areas: Collection<Rect>, tint: Int) ->
NotificationIconColorLookup { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
IconColorsImpl(tint, areas)
@@ -77,6 +81,8 @@
}
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** [NotificationIconsViewData] indicating which icons to display in the view. */
val icons: Flow<NotificationIconsViewData> =
@@ -88,6 +94,8 @@
)
}
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
/** An Icon to show "isolated" in the IconContainer. */
val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
@@ -99,6 +107,8 @@
}
.distinctUntilChanged()
.flowOn(bgContext)
+ .conflate()
+ .distinctUntilChanged()
.pairwise(initialValue = null)
.sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
val animate =
@@ -113,7 +123,7 @@
/** Location to show an isolated icon, if there is one. */
val isolatedIconLocation: Flow<Rect> =
- headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+ headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
private class IconColorsImpl(
override val tint: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 20c8add..6836816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -238,6 +238,9 @@
) {
val TAG = "AvalancheSuppressor"
+ override var reason: String = "avalanche"
+ protected set
+
enum class State {
ALLOW_CONVERSATION_AFTER_AVALANCHE,
ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -252,13 +255,13 @@
override fun shouldSuppress(entry: NotificationEntry): Boolean {
val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
- val state = allow(entry)
+ val state = calculateState(entry)
val suppress = isActive && state == State.SUPPRESS
reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
return suppress
}
- fun allow(entry: NotificationEntry): State {
+ private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
entry.sbn.notification.`when` > avalancheProvider.startTime
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 2f80c5d..ee79727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -85,7 +85,7 @@
/** A reason why visual interruptions might be suppressed based on the notification. */
abstract class VisualInterruptionFilter(
override val types: Set<VisualInterruptionType>,
- override var reason: String,
+ override val reason: String,
override val uiEventId: UiEventEnum? = null,
override val eventLogData: EventLogData? = null
) : VisualInterruptionSuppressor {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0a11eb2..ebe7fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -233,7 +233,7 @@
private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
- private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
+ private final StackStateAnimator mStateAnimator;
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
private boolean mChildTransferInProgress;
@@ -670,6 +670,7 @@
mExpandHelper.setScrollAdapter(mScrollAdapter);
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
+ mStateAnimator = new StackStateAnimator(context, this);
mShouldDrawNotificationBackground =
res.getBoolean(R.bool.config_drawNotificationBackground);
setOutlineProvider(mOutlineProvider);
@@ -752,6 +753,7 @@
}
public void setIsRemoteInputActive(boolean isActive) {
+ FooterViewRefactor.assertInLegacyMode();
mIsRemoteInputActive = isActive;
updateFooter();
}
@@ -764,6 +766,7 @@
@VisibleForTesting
public void updateFooter() {
+ FooterViewRefactor.assertInLegacyMode();
if (mFooterView == null || mController == null) {
return;
}
@@ -776,10 +779,12 @@
}
private boolean shouldShowDismissView() {
+ FooterViewRefactor.assertInLegacyMode();
return mController.hasActiveClearableNotifications(ROWS_ALL);
}
private boolean shouldShowFooterView(boolean showDismissView) {
+ FooterViewRefactor.assertInLegacyMode();
return (showDismissView || mController.getVisibleNotificationCount() > 0)
&& mIsCurrentUserSetup // see: b/193149550
&& !onKeyguard()
@@ -1079,6 +1084,7 @@
}
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mStackScrollAlgorithm.initView(context);
+ mStateAnimator.initView(context);
mAmbientState.reload(context);
mPaddingBetweenElements = Math.max(1,
res.getDimensionPixelSize(R.dimen.notification_divider_height));
@@ -4359,6 +4365,12 @@
layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
}
if (endPosition > layoutEnd) {
+ // if Scene Container is active, send bottom notification expansion delta
+ // to it so that it can scroll the stack and scrim accordingly.
+ if (SceneContainerFlag.isEnabled()) {
+ float diff = endPosition - layoutEnd;
+ mController.sendSyntheticScrollToSceneFramework(diff);
+ }
setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
mDisallowScrollingInThisMotion = true;
}
@@ -4723,9 +4735,6 @@
footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
});
}
- if (FooterViewRefactor.isEnabled()) {
- updateFooter();
- }
}
public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4790,16 +4799,15 @@
}
public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
+ FooterViewRefactor.assertInLegacyMode();
if (mFooterView == null || mNotificationStackSizeCalculator == null) {
return;
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- if (!FooterViewRefactor.isEnabled()) {
- mFooterView.showHistory(showHistory);
- mFooterView.setClearAllButtonVisible(showDismissView, animate);
- mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
- }
+ mFooterView.showHistory(showHistory);
+ mFooterView.setClearAllButtonVisible(showDismissView, animate);
+ mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
@VisibleForTesting
@@ -5070,7 +5078,7 @@
if (mOwnScrollY > 0) {
setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
}
- if (footerAffected) {
+ if (!FooterViewRefactor.isEnabled() && footerAffected) {
updateFooter();
}
}
@@ -5081,6 +5089,10 @@
}
private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+ // If scene container is active, NSSL should not control its own scrolling.
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
// Avoid Flicking during clear all
// when the shade finishes closing, onExpansionStopped will call
// resetScrollPosition to setOwnScrollY to 0
@@ -5176,6 +5188,7 @@
}
void setUpcomingStatusBarState(int upcomingStatusBarState) {
+ FooterViewRefactor.assertInLegacyMode();
mUpcomingStatusBarState = upcomingStatusBarState;
if (mUpcomingStatusBarState != mStatusBarState) {
updateFooter();
@@ -5193,7 +5206,9 @@
setDimmed(onKeyguard, fromShadeLocked);
setExpandingEnabled(!onKeyguard);
- updateFooter();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
requestChildrenUpdate();
onUpdateRowStates();
updateVisibility();
@@ -5270,8 +5285,11 @@
for (int i = 0; i < childCount; i++) {
ExpandableView child = getChildAtIndex(i);
child.dump(pw, args);
- if (child instanceof FooterView) {
- DumpUtilsKt.withIncreasedIndent(pw, () -> dumpFooterViewVisibility(pw));
+ if (!FooterViewRefactor.isEnabled()) {
+ if (child instanceof FooterView) {
+ DumpUtilsKt.withIncreasedIndent(pw,
+ () -> dumpFooterViewVisibility(pw));
+ }
}
pw.println();
}
@@ -5290,6 +5308,7 @@
}
private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+ FooterViewRefactor.assertInLegacyMode();
final boolean showDismissView = shouldShowDismissView();
pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
@@ -5988,6 +6007,7 @@
* Sets whether the current user is set up, which is required to show the footer (b/193149550)
*/
public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+ FooterViewRefactor.assertInLegacyMode();
if (mIsCurrentUserSetup != isCurrentUserSetup) {
mIsCurrentUserSetup = isCurrentUserSetup;
updateFooter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a2ff406..5fa0624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -314,8 +314,10 @@
// The bottom might change because we're using the final actual height of the view
mView.setAnimateBottomOnLayout(true);
}
- // Let's update the footer once the notifications have been updated (in the next frame)
- mView.post(this::updateFooter);
+ if (!FooterViewRefactor.isEnabled()) {
+ // Let's update the footer once the notifications have been updated (in the next frame)
+ mView.post(this::updateFooter);
+ }
};
@VisibleForTesting
@@ -342,8 +344,8 @@
mView.reinflateViews();
if (!FooterViewRefactor.isEnabled()) {
updateShowEmptyShadeView();
+ updateFooter();
}
- updateFooter();
}
@Override
@@ -389,7 +391,9 @@
@Override
public void onUpcomingStateChanged(int newState) {
- mView.setUpcomingStatusBarState(newState);
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setUpcomingStatusBarState(newState);
+ }
}
@Override
@@ -407,7 +411,9 @@
public void onUserChanged(int userId) {
updateSensitivenessWithAnimation(false);
mHistoryEnabled = null;
- updateFooter();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
}
};
@@ -810,14 +816,14 @@
if (!FooterViewRefactor.isEnabled()) {
mView.setFooterClearAllListener(() ->
mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+ mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
+ mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
+ @Override
+ public void onRemoteInputActive(boolean active) {
+ mView.setIsRemoteInputActive(active);
+ }
+ });
}
- mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
- mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
- @Override
- public void onRemoteInputActive(boolean active) {
- mView.setIsRemoteInputActive(active);
- }
- });
mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
final Runnable doCollapseRunnable = () ->
mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
@@ -871,7 +877,9 @@
switch (key) {
case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
mHistoryEnabled = null; // invalidate
- updateFooter();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
break;
case HIGH_PRIORITY:
mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
@@ -893,9 +901,11 @@
return kotlin.Unit.INSTANCE;
});
- // attach callback, and then call it to update mView immediately
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- mDeviceProvisionedListener.onDeviceProvisionedChanged();
+ if (!FooterViewRefactor.isEnabled()) {
+ // attach callback, and then call it to update mView immediately
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ mDeviceProvisionedListener.onDeviceProvisionedChanged();
+ }
if (screenshareNotificationHiding()) {
mSensitiveNotificationProtectionController
@@ -1106,8 +1116,7 @@
}
public int getVisibleNotificationCount() {
- // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer
- // visibility in the refactored code
+ FooterViewRefactor.assertInLegacyMode();
return mNotifStats.getNumActiveNotifs();
}
@@ -1142,6 +1151,11 @@
}
}
+ /** Send internal notification expansion to the scene container framework. */
+ public void sendSyntheticScrollToSceneFramework(Float delta) {
+ mStackAppearanceInteractor.setSyntheticScroll(delta);
+ }
+
/** Get the y-coordinate of the top bound of the stack. */
public float getPlaceholderTop() {
return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
@@ -1509,14 +1523,14 @@
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
- // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
- // visibility in the refactored code
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+ // section clear action in the new stack.
return hasNotifications(selection, true /* clearable */);
}
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
- // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
- // visibility in the refactored code
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+ // section clear action in the new stack.
boolean hasAlertingMatchingClearable = isClearable
? mNotifStats.getHasClearableAlertingNotifs()
: mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1558,7 +1572,9 @@
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
- updateFooter();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
}
public void lockScrollTo(NotificationEntry entry) {
@@ -1573,6 +1589,7 @@
}
public void updateFooter() {
+ FooterViewRefactor.assertInLegacyMode();
Trace.beginSection("NSSLC.updateFooter");
mView.updateFooter();
Trace.endSection();
@@ -2134,19 +2151,16 @@
private class NotifStackControllerImpl implements NotifStackController {
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
- // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
- // is handled in the refactored stack.
+ // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+ // section clear action in the new stack.
mNotifStats = notifStats;
if (!FooterViewRefactor.isEnabled()) {
mView.setHasFilteredOutSeenNotifications(
mSeenNotificationsInteractor
.getHasFilteredOutSeenNotifications().getValue());
- }
- updateFooter();
-
- if (!FooterViewRefactor.isEnabled()) {
+ updateFooter();
updateShowEmptyShadeView();
updateImportantForAccessibility();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 664a6b6..15fde0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -137,7 +138,7 @@
}
private void updateAlphaState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
final boolean isHunGoingToShade = ambientState.isShadeExpanded()
@@ -295,7 +296,7 @@
}
private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
- int speedBumpIndex) {
+ int speedBumpIndex) {
int childCount = algorithmState.visibleChildren.size();
int belowSpeedBump = speedBumpIndex;
for (int i = 0; i < childCount; i++) {
@@ -322,7 +323,7 @@
}
private void updateClipping(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
float drawStart = ambientState.isOnKeyguard() ? 0
: ambientState.getStackY() - ambientState.getScrollY();
float clipStart = 0;
@@ -454,7 +455,7 @@
}
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
- ExpandableView v) {
+ ExpandableView v) {
ExpandableViewState viewState = v.getViewState();
viewState.notGoneIndex = notGoneIndex;
state.visibleChildren.add(v);
@@ -480,7 +481,7 @@
* @param ambientState The current ambient state
*/
protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
if (!ambientState.isOnKeyguard()
|| (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -494,7 +495,7 @@
}
private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
- int i) {
+ int i) {
expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
if (currentYPosition <= 0) {
expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -598,18 +599,31 @@
viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
);
if (view instanceof FooterView) {
- final boolean shadeClosed = !ambientState.isShadeExpanded();
- final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
- if (shadeClosed) {
- viewState.hidden = true;
- } else {
+ if (FooterViewRefactor.isEnabled()) {
final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+ // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an
+ // emission when clearAllNotifications is called, and then use that in the footer
+ // visibility flow.
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter
- || (ambientState.isClearAllInProgress()
+ noSpaceForFooter || (ambientState.isClearAllInProgress()
&& !hasNonClearableNotifs(algorithmState));
+
+ } else {
+ final boolean shadeClosed = !ambientState.isShadeExpanded();
+ final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
+ if (shadeClosed) {
+ viewState.hidden = true;
+ } else {
+ final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ + view.getIntrinsicHeight();
+ final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+ ((FooterView.FooterViewState) viewState).hideContent =
+ isShelfShowing || noSpaceForFooter
+ || (ambientState.isClearAllInProgress()
+ && !hasNonClearableNotifs(algorithmState));
+ }
}
} else {
if (view instanceof EmptyShadeView) {
@@ -731,7 +745,7 @@
@VisibleForTesting
void updatePulsingStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) {
@@ -761,7 +775,7 @@
}
private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
// Move the tracked heads up into position during the appear animation, by interpolating
@@ -870,18 +884,18 @@
boolean shouldHunBeVisibleWhenScrolled(boolean mustStayOnScreen, boolean headsUpIsVisible,
boolean showingPulsing, boolean isOnKeyguard, boolean headsUpOnKeyguard) {
return mustStayOnScreen && !headsUpIsVisible
- && !showingPulsing
- && (!isOnKeyguard || headsUpOnKeyguard);
+ && !showingPulsing
+ && (!isOnKeyguard || headsUpOnKeyguard);
}
- /**
+ /**
* When shade is open and we are scrolled to the bottom of notifications,
* clamp incoming HUN in its collapsed form, right below qs offset.
* Transition pinned collapsed HUN to full height when scrolling back up.
*/
@VisibleForTesting
void clampHunToTop(float clampInset, float stackTranslation, float collapsedHeight,
- ExpandableViewState viewState) {
+ ExpandableViewState viewState) {
final float newTranslation = Math.max(clampInset + stackTranslation,
viewState.getYTranslation());
@@ -896,7 +910,7 @@
// Pin HUN to bottom of expanded QS
// while the rest of notifications are scrolled offscreen.
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
- ExpandableViewState childState) {
+ ExpandableViewState childState) {
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -919,7 +933,7 @@
@VisibleForTesting
float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
- float viewMaxHeight, float originalCornerRadius) {
+ float viewMaxHeight, float originalCornerRadius) {
// Compute y where corner roundness should be in its original unpinned state.
// We use view max height because the pinned collapsed HUN expands to max height
@@ -948,7 +962,7 @@
* @param ambientState The ambient state of the algorithm
*/
private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
float childrenOnTop = 0.0f;
@@ -976,13 +990,13 @@
* vertically top of screen. Top HUNs should have drop shadows
* @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
* @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
- * that overlaps with QQS Panel. The integer part represents the count of
- * previous HUNs whose Z positions are greater than 0.
+ * that overlaps with QQS Panel. The integer part represents the count of
+ * previous HUNs whose Z positions are greater than 0.
*/
protected float updateChildZValue(int i, float childrenOnTop,
- StackScrollAlgorithmState algorithmState,
- AmbientState ambientState,
- boolean isTopHun) {
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState,
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index b38d619..ab62ed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -23,6 +23,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.content.Context;
import android.util.Property;
import android.view.View;
@@ -66,9 +67,8 @@
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
private static final int MAX_STAGGER_COUNT = 5;
- private final int mGoToFullShadeAppearingTranslation;
- @VisibleForTesting
- float mHeadsUpAppearStartAboveScreen;
+ @VisibleForTesting int mGoToFullShadeAppearingTranslation;
+ @VisibleForTesting float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -92,14 +92,9 @@
private NotificationShelf mShelf;
private StackStateLogger mLogger;
- public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
+ public StackStateAnimator(Context context, NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
- // TODO(b/317061579) reload on configuration changes
- mGoToFullShadeAppearingTranslation =
- hostLayout.getContext().getResources().getDimensionPixelSize(
- R.dimen.go_to_full_shade_appearing_translation);
- mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
- .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+ initView(context);
mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -118,6 +113,21 @@
};
}
+ /**
+ * Needs to be called on configuration changes, to update cached resource values.
+ */
+ public void initView(Context context) {
+ updateResources(context);
+ }
+
+ private void updateResources(Context context) {
+ mGoToFullShadeAppearingTranslation =
+ context.getResources().getDimensionPixelSize(
+ R.dimen.go_to_full_shade_appearing_translation);
+ mHeadsUpAppearStartAboveScreen = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+ }
+
protected void setLogger(StackStateLogger logger) {
mLogger = logger;
}
@@ -460,15 +470,8 @@
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
- if (event.headsUpFromBottom) {
- // start from the bottom of the screen
- mTmpState.setYTranslation(
- mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
- } else {
- // start from the top of the screen
- mTmpState.setYTranslation(
- -mStackTopMargin - mHeadsUpAppearStartAboveScreen);
- }
+ // translate the HUN in from the top, or the bottom of the screen
+ mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
// set the height and the initial position
mTmpState.applyToView(changingView);
mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
@@ -512,12 +515,20 @@
|| event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
mHeadsUpDisappearChildren.add(changingView);
Runnable endRunnable = null;
+ mTmpState.copyFrom(changingView.getViewState());
if (changingView.getParent() == null) {
// This notification was actually removed, so we need to add it
// transiently
mHostLayout.addTransientView(changingView, 0);
changingView.setTransientContainer(mHostLayout);
- mTmpState.initFrom(changingView);
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // StackScrollAlgorithm cannot find this view because it has been removed
+ // from the NSSL. To correctly translate the view to the top or bottom of
+ // the screen (where it animated from), we need to update its translation.
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(event.headsUpFromBottom)
+ );
+ }
endRunnable = changingView::removeFromTransientContainer;
}
@@ -565,16 +576,19 @@
changingView.setInRemovalAnimation(true);
};
}
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
- Interpolators.FAST_OUT_SLOW_IN_REVERSE);
- }
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
startAnimation, postAnimation,
getGlobalAnimationFinishedListener());
mAnimationProperties.delay += removeAnimationDelay;
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+ mAnimationProperties.getAnimationFilter().animateY = true;
+ mTmpState.animateTo(changingView, mAnimationProperties);
+ }
} else if (endRunnable != null) {
endRunnable.run();
}
@@ -585,6 +599,15 @@
return needsCustomAnimation;
}
+ private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen;
+ }
+ // start from the top of the screen
+ return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
+ }
+
public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
final boolean isRubberbanded) {
final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 311ba83..9efe632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -47,4 +47,11 @@
* further.
*/
val scrolledToTop = MutableStateFlow(true)
+
+ /**
+ * The amount in px that the notification stack should scroll due to internal expansion. This
+ * should only happen when a notification expansion hits the bottom of the screen, so it is
+ * necessary to scroll up to keep expanding the notification.
+ */
+ val syntheticScroll = MutableStateFlow(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 9984ba9..08df473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -50,6 +51,13 @@
*/
val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
+ /**
+ * The amount in px that the notification stack should scroll due to internal expansion. This
+ * should only happen when a notification expansion hits the bottom of the screen, so it is
+ * necessary to scroll up to keep expanding the notification.
+ */
+ val syntheticScroll: Flow<Float> = repository.syntheticScroll.asStateFlow()
+
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
@@ -70,4 +78,9 @@
fun setScrolledToTop(scrolledToTop: Boolean) {
repository.scrolledToTop.value = scrolledToTop
}
+
+ /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */
+ fun setSyntheticScroll(delta: Float) {
+ repository.syntheticScroll.value = delta
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 4d65b9d..883aa9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -18,11 +18,10 @@
import android.view.LayoutInflater
import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.traceSection
+import com.android.app.tracing.TraceUtils.traceAsync
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.common.ui.reinflateAndBindLatest
import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -33,6 +32,7 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
@@ -43,12 +43,18 @@
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import java.util.Optional
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
@@ -83,7 +89,7 @@
bindHideList(viewController, viewModel, hiderTracker)
if (FooterViewRefactor.isEnabled) {
- launch { bindFooter(view) }
+ launch { reinflateAndBindFooter(view) }
launch { bindEmptyShade(view) }
launch {
viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
@@ -108,42 +114,61 @@
)
}
- private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+ private suspend fun reinflateAndBindFooter(parentView: NotificationStackScrollLayout) {
viewModel.footer.getOrNull()?.let { footerViewModel ->
// The footer needs to be re-inflated every time the theme or the font size changes.
- configuration.reinflateAndBindLatest(
- R.layout.status_bar_notification_footer,
- parentView,
- attachToRoot = false,
- backgroundDispatcher,
- ) { footerView: FooterView ->
- traceSection("bind FooterView") {
- val disposableHandle =
- FooterViewBinder.bindWhileAttached(
- footerView,
- footerViewModel,
- clearAllNotifications = {
- metricsLogger.action(
- MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
- )
- parentView.clearAllNotifications()
- },
- launchNotificationSettings = { view ->
- notificationActivityStarter
- .get()
- .startHistoryIntent(view, /* showHistory = */ false)
- },
- launchNotificationHistory = { view ->
- notificationActivityStarter
- .get()
- .startHistoryIntent(view, /* showHistory = */ true)
- },
- )
- parentView.setFooterView(footerView)
- return@reinflateAndBindLatest disposableHandle
+ configuration
+ .inflateLayout<FooterView>(
+ R.layout.status_bar_notification_footer,
+ parentView,
+ attachToRoot = false,
+ )
+ .flowOn(backgroundDispatcher)
+ .collectLatest { footerView: FooterView ->
+ traceAsync("bind FooterView") {
+ parentView.setFooterView(footerView)
+ bindFooter(footerView, footerViewModel, parentView)
+ }
}
+ }
+ }
+
+ /**
+ * Binds the footer (including its visibility) and dispose of the [DisposableHandle] when done.
+ */
+ private suspend fun bindFooter(
+ footerView: FooterView,
+ footerViewModel: FooterViewModel,
+ parentView: NotificationStackScrollLayout
+ ): Unit = coroutineScope {
+ val disposableHandle =
+ FooterViewBinder.bindWhileAttached(
+ footerView,
+ footerViewModel,
+ clearAllNotifications = {
+ metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
+ parentView.clearAllNotifications()
+ },
+ launchNotificationSettings = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ false)
+ },
+ launchNotificationHistory = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ true)
+ },
+ )
+ launch {
+ viewModel.shouldShowFooterView.collect { animatedVisibility ->
+ footerView.setVisible(
+ /* visible = */ animatedVisibility.value,
+ /* animate = */ animatedVisibility.isAnimating,
+ )
}
}
+ disposableHandle.awaitCancellationThenDispose()
}
private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 814146c..a157785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -69,6 +69,9 @@
controller.setMaxAlphaForExpansion(
((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
)
+ if (expandFraction == 0f || expandFraction == 1f) {
+ controller.onExpansionStopped()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 86c0a678..a6c6586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,21 +16,32 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for the list of notifications. */
@@ -42,9 +53,13 @@
val footer: Optional<FooterViewModel>,
val logger: Optional<NotificationLoggerViewModel>,
activeNotificationsInteractor: ActiveNotificationsInteractor,
+ keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ powerInteractor: PowerInteractor,
+ remoteInputInteractor: RemoteInputInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
+ userSetupInteractor: UserSetupInteractor,
zenModeInteractor: ZenModeInteractor,
) {
/**
@@ -76,6 +91,10 @@
combine(
activeNotificationsInteractor.areAnyNotificationsPresent,
shadeInteractor.isQsFullscreen,
+ // TODO(b/293167744): It looks like we're essentially trying to check the same
+ // things for the empty shade visibility as we do for the footer, just in a
+ // slightly different way. We should change this so we also check
+ // statusBarState and isAwake instead of specific keyguard transitions.
keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
emit(false)
},
@@ -97,6 +116,80 @@
}
}
+ val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(AnimatedValue.NotAnimating(false))
+ } else {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ userSetupInteractor.isUserSetUp,
+ keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+ shadeInteractor.qsExpansion,
+ shadeInteractor.isQsFullscreen,
+ powerInteractor.isAsleep,
+ remoteInputInteractor.isRemoteInputActive,
+ shadeInteractor.shadeExpansion.map { it == 0f }
+ ) {
+ hasNotifications,
+ isUserSetUp,
+ isOnKeyguard,
+ qsExpansion,
+ qsFullScreen,
+ isAsleep,
+ isRemoteInputActive,
+ isShadeClosed ->
+ Pair(
+ // Should the footer be visible?
+ when {
+ !hasNotifications -> false
+ // Hide the footer until the user setup is complete, to prevent access
+ // to settings (b/193149550).
+ !isUserSetUp -> false
+ // Do not show the footer if the lockscreen is visible (incl. AOD),
+ // except if the shade is opened on top. See also b/219680200.
+ isOnKeyguard -> false
+ // Make sure we're not showing the footer in the transition to AOD while
+ // going to sleep (b/190227875). The StatusBarState is unfortunately not
+ // updated quickly enough when the power button is pressed, so this is
+ // necessary in addition to the isOnKeyguard check.
+ isAsleep -> false
+ // Do not show the footer if quick settings are fully expanded (except
+ // for the foldable split shade view). See b/201427195 && b/222699879.
+ qsExpansion == 1f && qsFullScreen -> false
+ // Hide the footer if remote input is active (i.e. user is replying to a
+ // notification). See b/75984847.
+ isRemoteInputActive -> false
+ // Never show the footer if the shade is collapsed (e.g. when HUNing).
+ isShadeClosed -> false
+ else -> true
+ },
+ // This could in theory be in the .sample below, but it tends to be
+ // inconsistent, so we're passing it on to make sure we have the same state.
+ isOnKeyguard
+ )
+ }
+ .distinctUntilChanged()
+ // Should we animate the visibility change?
+ .sample(
+ // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+ // but instead it should be a field in ShadeAnimationInteractor.
+ combine(
+ shadeInteractor.isShadeFullyExpanded,
+ shadeInteractor.isShadeTouchable,
+ ::Pair
+ )
+ .onStart { emit(Pair(false, false)) }
+ ) { (visible, isOnKeyguard), (isShadeFullyExpanded, animationsEnabled) ->
+ // Animate if the shade is interactive, but NOT on the lockscreen. Having
+ // animations enabled while on the lockscreen makes the footer appear briefly
+ // when transitioning between the shade and keyguard.
+ val shouldAnimate = isShadeFullyExpanded && animationsEnabled && !isOnKeyguard
+ AnimatableEvent(visible, shouldAnimate)
+ }
+ .toAnimatedValueFlow()
+ }
+ }
+
// TODO(b/308591475): This should be tracked separately by the empty shade.
val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index bdf1a64..3a0f03f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -28,6 +28,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -45,31 +46,32 @@
*/
val expandFraction: Flow<Float> =
combine(
- shadeInteractor.shadeExpansion,
- sceneInteractor.transitionState,
- ) { shadeExpansion, transitionState ->
- when (transitionState) {
- is ObservableTransitionState.Idle -> {
- if (transitionState.scene == SceneKey.Lockscreen) {
- 1f
- } else {
- shadeExpansion
+ shadeInteractor.shadeExpansion,
+ sceneInteractor.transitionState,
+ ) { shadeExpansion, transitionState ->
+ when (transitionState) {
+ is ObservableTransitionState.Idle -> {
+ if (transitionState.scene == SceneKey.Lockscreen) {
+ 1f
+ } else {
+ shadeExpansion
+ }
}
- }
- is ObservableTransitionState.Transition -> {
- if (
- (transitionState.fromScene == SceneKey.Shade &&
- transitionState.toScene == SceneKey.QuickSettings) ||
- (transitionState.fromScene == SceneKey.QuickSettings &&
- transitionState.toScene == SceneKey.Shade)
- ) {
- 1f
- } else {
- shadeExpansion
+ is ObservableTransitionState.Transition -> {
+ if (
+ (transitionState.fromScene == SceneKey.Shade &&
+ transitionState.toScene == SceneKey.QuickSettings) ||
+ (transitionState.fromScene == SceneKey.QuickSettings &&
+ transitionState.toScene == SceneKey.Shade)
+ ) {
+ 1f
+ } else {
+ shadeExpansion
+ }
}
}
}
- }
+ .distinctUntilChanged()
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 65d9c9f..7ac5cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -86,6 +86,13 @@
*/
val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+ /**
+ * The amount in px that the notification stack should scroll due to internal expansion. This
+ * should only happen when a notification expansion hits the bottom of the screen, so it is
+ * necessary to scroll up to keep expanding the notification.
+ */
+ val syntheticScroll: Flow<Float> = interactor.syntheticScroll
+
/** Sets the y-coord in px of the top of the contents of the notification stack. */
fun onContentTopChanged(padding: Float) {
interactor.setContentTop(padding)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 3915c376..811da51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -31,20 +32,24 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -70,6 +75,7 @@
import kotlinx.coroutines.isActive
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
+@SysUISingleton
class SharedNotificationContainerViewModel
@Inject
constructor(
@@ -80,6 +86,9 @@
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -94,6 +103,12 @@
mapOf<Edge?, Flow<Float>>(
Edge(from = LOCKSCREEN, to = DREAMING) to
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = GONE) to
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = ALTERNATE_BOUNCER, to = GONE) to
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = PRIMARY_BOUNCER, to = GONE) to
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
Edge(from = DREAMING, to = LOCKSCREEN) to
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
Edge(from = LOCKSCREEN, to = OCCLUDED) to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 8a56da3..80d45fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,8 +30,8 @@
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator.PendingIntentStarter
import com.android.systemui.animation.DelegateLaunchAnimatorController
import com.android.systemui.assist.AssistManager
import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
@@ -75,7 +75,7 @@
private val shadeAnimationInteractor: ShadeAnimationInteractor,
private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
private val context: Context,
@DisplayId private val displayId: Int,
private val lockScreenUserManager: NotificationLockscreenUserManager,
@@ -127,7 +127,7 @@
override fun startPendingIntentDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable?,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
) {
activityStarterInternal.startPendingIntentDismissingKeyguard(
intent = intent,
@@ -139,7 +139,7 @@
override fun startPendingIntentMaybeDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable?,
- animationController: ActivityLaunchAnimator.Controller?
+ animationController: ActivityTransitionAnimator.Controller?
) {
activityStarterInternal.startPendingIntentDismissingKeyguard(
intent = intent,
@@ -209,7 +209,7 @@
override fun startActivity(
intent: Intent,
dismissShade: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
showOverLockscreenWhenLocked: Boolean,
) {
activityStarterInternal.startActivity(
@@ -222,7 +222,7 @@
override fun startActivity(
intent: Intent,
dismissShade: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
showOverLockscreenWhenLocked: Boolean,
userHandle: UserHandle?,
) {
@@ -245,7 +245,7 @@
override fun postStartActivityDismissingKeyguard(
intent: PendingIntent,
- animationController: ActivityLaunchAnimator.Controller?
+ animationController: ActivityTransitionAnimator.Controller?
) {
postOnUiThread {
activityStarterInternal.startPendingIntentDismissingKeyguard(
@@ -268,7 +268,7 @@
override fun postStartActivityDismissingKeyguard(
intent: Intent,
delay: Int,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
) {
postOnUiThread(delay) {
activityStarterInternal.startActivityDismissingKeyguard(
@@ -283,7 +283,7 @@
override fun postStartActivityDismissingKeyguard(
intent: Intent,
delay: Int,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
customMessage: String?,
) {
postOnUiThread(delay) {
@@ -342,7 +342,7 @@
disallowEnterPictureInPictureWhileLaunching: Boolean,
callback: ActivityStarter.Callback?,
flags: Int,
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
userHandle: UserHandle?,
) {
activityStarterInternal.startActivityDismissingKeyguard(
@@ -430,7 +430,7 @@
disallowEnterPictureInPictureWhileLaunching: Boolean = false,
callback: ActivityStarter.Callback? = null,
flags: Int = 0,
- animationController: ActivityLaunchAnimator.Controller? = null,
+ animationController: ActivityTransitionAnimator.Controller? = null,
userHandle: UserHandle? = null,
customMessage: String? = null,
) {
@@ -464,7 +464,7 @@
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
intent.addFlags(flags)
val result = intArrayOf(ActivityManager.START_CANCELED)
- activityLaunchAnimator.startIntentWithAnimation(
+ activityTransitionAnimator.startIntentWithAnimation(
animController,
animate,
intent.getPackage()
@@ -552,7 +552,7 @@
intent: PendingIntent,
intentSentUiThreadCallback: Runnable? = null,
associatedView: View? = null,
- animationController: ActivityLaunchAnimator.Controller? = null,
+ animationController: ActivityTransitionAnimator.Controller? = null,
showOverLockscreen: Boolean = false,
) {
val animationController =
@@ -602,7 +602,7 @@
val collapse = !animate
val runnable = Runnable {
try {
- activityLaunchAnimator.startPendingIntentWithAnimation(
+ activityTransitionAnimator.startPendingIntentWithAnimation(
controller,
animate,
intent.creatorPackage,
@@ -670,7 +670,7 @@
fun startActivity(
intent: Intent,
dismissShade: Boolean = false,
- animationController: ActivityLaunchAnimator.Controller? = null,
+ animationController: ActivityTransitionAnimator.Controller? = null,
showOverLockscreenWhenLocked: Boolean = false,
userHandle: UserHandle? = null,
) {
@@ -698,7 +698,7 @@
showOverLockscreenWhenLocked
) == true
- var controller: ActivityLaunchAnimator.Controller? = null
+ var controller: ActivityTransitionAnimator.Controller? = null
if (animate) {
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
@@ -721,7 +721,7 @@
centralSurfaces?.awakenDreams()
}
- activityLaunchAnimator.startIntentWithAnimation(
+ activityTransitionAnimator.startIntentWithAnimation(
controller,
animate,
intent.getPackage(),
@@ -815,7 +815,7 @@
}
/**
- * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that:
+ * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
* - if it launches in the notification shade window and `dismissShade` is true, then the
* shade will be instantly dismissed at the end of the animation.
* - if it launches in status bar window, it will make the status bar window match the
@@ -830,15 +830,15 @@
* @param isLaunchForActivity whether the launch is for an activity.
*/
private fun wrapAnimationControllerForShadeOrStatusBar(
- animationController: ActivityLaunchAnimator.Controller?,
+ animationController: ActivityTransitionAnimator.Controller?,
dismissShade: Boolean,
isLaunchForActivity: Boolean,
- ): ActivityLaunchAnimator.Controller? {
+ ): ActivityTransitionAnimator.Controller? {
if (animationController == null) {
return null
}
- val rootView = animationController.launchContainer.rootView
- val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
+ val rootView = animationController.transitionContainer.rootView
+ val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
statusBarWindowController.wrapAnimationControllerIfInStatusBar(
rootView,
animationController
@@ -870,8 +870,8 @@
* lockscreen, the correct flags are set for it to be occluded.
*/
private fun wrapAnimationControllerForLockscreen(
- animationController: ActivityLaunchAnimator.Controller?
- ): ActivityLaunchAnimator.Controller? {
+ animationController: ActivityTransitionAnimator.Controller?
+ ): ActivityTransitionAnimator.Controller? {
return animationController?.let {
object : DelegateLaunchAnimatorController(it) {
override fun onIntentStarted(willAnimate: Boolean) {
@@ -881,8 +881,8 @@
}
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- super.onLaunchAnimationStart(isExpandingFullyAbove)
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ super.onTransitionAnimationStart(isExpandingFullyAbove)
// Double check that the keyguard is still showing and not going
// away, but if so set the keyguard occluded. Typically, WM will let
@@ -902,17 +902,19 @@
}
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
// Set mIsLaunchingActivityOverLockscreen to false before actually
// finishing the animation so that we can assume that
// mIsLaunchingActivityOverLockscreen being true means that we will
// collapse the shade (or at least run the post collapse runnables)
// later on.
centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
}
- override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ override fun onTransitionAnimationCancelled(
+ newKeyguardOccludedState: Boolean?
+ ) {
if (newKeyguardOccludedState != null) {
keyguardViewMediatorLazy
.get()
@@ -925,7 +927,7 @@
// collapse the shade (or at least run the // post collapse
// runnables) later on.
centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 4019436..9052409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -38,7 +38,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.display.data.repository.DisplayMetricsRepository;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -334,6 +334,6 @@
/**
* Gets an animation controller from a notification row.
*/
- ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+ ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification(
ExpandableNotificationRow associatedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 60dfaa7..8af7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -20,7 +20,7 @@
import android.view.MotionEvent
import androidx.lifecycle.LifecycleRegistry
import com.android.keyguard.AuthKeyguardMessageArea
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.navigationbar.NavigationBarView
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.qs.QSPanelController
@@ -99,5 +99,5 @@
override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {}
override fun getAnimatorControllerFromNotification(
associatedView: ExpandableNotificationRow?,
- ): ActivityLaunchAnimator.Controller? = null
+ ): ActivityTransitionAnimator.Controller? = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2099361..35aa3df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,6 +28,7 @@
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -114,7 +115,7 @@
import com.android.systemui.InitController;
import com.android.systemui.Prefs;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.back.domain.interactor.BackActionInteractor;
import com.android.systemui.biometrics.AuthRippleController;
@@ -575,7 +576,7 @@
private boolean mNoAnimationOnNextBarModeChange;
private final SysuiStatusBarStateController mStatusBarStateController;
- private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final ActivityTransitionAnimator mActivityTransitionAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
private final Lazy<NotificationPresenter> mPresenterLazy;
private final Lazy<NotificationActivityStarter> mNotificationActivityStarterLazy;
@@ -693,7 +694,7 @@
@Main MessageRouter messageRouter,
WallpaperManager wallpaperManager,
Optional<StartingSurface> startingSurfaceOptional,
- ActivityLaunchAnimator activityLaunchAnimator,
+ ActivityTransitionAnimator activityTransitionAnimator,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager,
@@ -815,7 +816,7 @@
shadeExpansionListener.onPanelExpansionChanged(currentState);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityTransitionAnimator = activityTransitionAnimator;
// TODO(b/190746471): Find a better home for this.
DateTimeView.setReceiverHandler(timeTickHandler);
@@ -1423,8 +1424,8 @@
private void setUpPresenter() {
// Set up the initial notification state.
- mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
- mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
+ mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback);
+ mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener);
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mStackScrollerController.setNotificationActivityStarter(
mNotificationActivityStarterLazy.get());
@@ -2497,7 +2498,8 @@
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- if (shouldAnimateDozeWakeup()) {
+ boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled();
+ if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
// If this is false, the power button must be physically pressed in order to
// trigger fingerprint authentication.
final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
@@ -3174,8 +3176,8 @@
}
};
- private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback =
- new ActivityLaunchAnimator.Callback() {
+ private final ActivityTransitionAnimator.Callback mActivityTransitionAnimatorCallback =
+ new ActivityTransitionAnimator.Callback() {
@Override
public boolean isOnKeyguard() {
return mKeyguardStateController.isShowing();
@@ -3203,15 +3205,15 @@
}
};
- private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
- new ActivityLaunchAnimator.Listener() {
+ private final ActivityTransitionAnimator.Listener mActivityTransitionAnimatorListener =
+ new ActivityTransitionAnimator.Listener() {
@Override
- public void onLaunchAnimationStart() {
+ public void onTransitionAnimationStart() {
mKeyguardViewMediator.setBlursDisabledForAppLaunch(true);
}
@Override
- public void onLaunchAnimationEnd() {
+ public void onTransitionAnimationEnd() {
mKeyguardViewMediator.setBlursDisabledForAppLaunch(false);
}
};
@@ -3265,7 +3267,7 @@
}
@Override
- public ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+ public ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification(
ExpandableNotificationRow associatedView) {
return mNotificationAnimationProvider.getAnimatorController(associatedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index be5c6b3..8e3d678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -349,8 +349,12 @@
}
}
if (child instanceof StatusBarIconView) {
- ((StatusBarIconView) child).updateIconDimens();
- if (!NotificationIconContainerRefactor.isEnabled()) {
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (!mChangingViewPositions) {
+ ((StatusBarIconView) child).updateIconDimens();
+ }
+ } else {
+ ((StatusBarIconView) child).updateIconDimens();
((StatusBarIconView) child).setDozing(mDozing, false, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 8ca5bfc..7ff5f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -1,25 +1,25 @@
package com.android.systemui.statusbar.phone
import android.view.View
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
/**
- * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
+ * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the right
* time.
*/
class StatusBarLaunchAnimatorController(
- private val delegate: ActivityLaunchAnimator.Controller,
+ private val delegate: ActivityTransitionAnimator.Controller,
private val shadeViewController: ShadeViewController,
private val shadeAnimationInteractor: ShadeAnimationInteractor,
private val shadeController: ShadeController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val isLaunchForActivity: Boolean = true
-) : ActivityLaunchAnimator.Controller by delegate {
+) : ActivityTransitionAnimator.Controller by delegate {
// Always sync the opening window with the shade, given that we draw a hole punch in the shade
// of the same size and position as the opening app to make it visible.
override val openingWindowSyncView: View?
@@ -34,32 +34,32 @@
}
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
shadeAnimationInteractor.setIsLaunchingActivity(true)
if (!isExpandingFullyAbove) {
shadeViewController.collapseWithDuration(
- ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
+ ActivityTransitionAnimator.TIMINGS.totalDuration.toInt())
}
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
shadeAnimationInteractor.setIsLaunchingActivity(false)
shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
- delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+ delegate.onTransitionAnimationProgress(state, progress, linearProgress)
shadeViewController.applyLaunchAnimationProgress(linearProgress)
}
- override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
- delegate.onLaunchAnimationCancelled()
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ delegate.onTransitionAnimationCancelled()
shadeAnimationInteractor.setIsLaunchingActivity(false)
shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4ee061d..2737580 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -52,7 +52,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -125,7 +125,7 @@
private final NotificationPresenter mPresenter;
private final ShadeViewController mShadeViewController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final ActivityTransitionAnimator mActivityTransitionAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
private final PowerInteractor mPowerInteractor;
private final UserTracker mUserTracker;
@@ -161,7 +161,7 @@
NotificationPresenter presenter,
ShadeViewController shadeViewController,
NotificationShadeWindowController notificationShadeWindowController,
- ActivityLaunchAnimator activityLaunchAnimator,
+ ActivityTransitionAnimator activityTransitionAnimator,
ShadeAnimationInteractor shadeAnimationInteractor,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
@@ -194,7 +194,7 @@
mOnUserInteractionCallback = onUserInteractionCallback;
mPresenter = presenter;
mShadeViewController = shadeViewController;
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityTransitionAnimator = activityTransitionAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
mPowerInteractor = powerInteractor;
mUserTracker = userTracker;
@@ -440,7 +440,7 @@
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry);
try {
- ActivityLaunchAnimator.Controller animationController =
+ ActivityTransitionAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row, null),
mShadeViewController,
@@ -448,7 +448,7 @@
mShadeController,
mNotificationShadeWindowController,
isActivityIntent);
- mActivityLaunchAnimator.startPendingIntentWithAnimation(
+ mActivityTransitionAnimator.startPendingIntentWithAnimation(
animationController,
animate,
intent.getCreatorPackage(),
@@ -482,7 +482,7 @@
@Override
public boolean onDismiss() {
AsyncTask.execute(() -> {
- ActivityLaunchAnimator.Controller animationController =
+ ActivityTransitionAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row),
mShadeViewController,
@@ -491,7 +491,7 @@
mNotificationShadeWindowController,
true /* isActivityIntent */);
- mActivityLaunchAnimator.startIntentWithAnimation(
+ mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intent.getPackage(),
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
@@ -528,11 +528,11 @@
tsb.addNextIntent(intent);
}
- ActivityLaunchAnimator.Controller viewController =
- ActivityLaunchAnimator.Controller.fromView(view,
+ ActivityTransitionAnimator.Controller viewController =
+ ActivityTransitionAnimator.Controller.fromView(view,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
);
- ActivityLaunchAnimator.Controller animationController =
+ ActivityTransitionAnimator.Controller animationController =
viewController == null ? null
: new StatusBarLaunchAnimatorController(
viewController,
@@ -542,8 +542,8 @@
mNotificationShadeWindowController,
true /* isActivityIntent */);
- mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
- intent.getPackage(),
+ mActivityTransitionAnimator.startIntentWithAnimation(
+ animationController, animate, intent.getPackage(),
(adapter) -> tsb.startActivities(
getActivityOptions(mDisplayId, adapter),
mUserTracker.getUserHandle()));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
index 246645e..72f4540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -15,20 +15,14 @@
*/
package com.android.systemui.statusbar.phone.domain.interactor
-import android.graphics.Rect
import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import com.android.systemui.statusbar.phone.domain.model.DarkState
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** States pertaining to calculating colors for icons in dark mode. */
class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
- /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
- val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
- /**
- * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
- */
- val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
- /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
- val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+ /** Dark-mode state for tinting icons. */
+ val darkState: Flow<DarkState> = repository.darkState.map { DarkState(it.areas, it.tint) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt
new file mode 100644
index 0000000..3cab7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.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.systemui.statusbar.phone.domain.model
+
+import android.graphics.Rect
+
+/** Dark mode visual states. */
+data class DarkState(
+ /** Areas on screen that require a dark-mode adjustment. */
+ val areas: Collection<Rect>,
+ /** Tint color to apply to UI elements that fall within [areas]. */
+ val tint: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 0bdd1a5..a20468f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -30,7 +30,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -233,7 +233,7 @@
logger.logChipClicked()
activityStarter.postStartActivityDismissingKeyguard(
intent,
- ActivityLaunchAnimator.Controller.fromView(
+ ActivityTransitionAnimator.Controller.fromView(
backgroundView,
InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
index 63566ee..e1798d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.satellite.ui.model
+import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -36,7 +37,10 @@
SatelliteConnectionState.On ->
Icon.Resource(
res = R.drawable.ic_satellite_not_connected,
- contentDescription = null,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_available
+ ),
)
SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
}
@@ -51,15 +55,36 @@
// TODO(b/316634365): these need content descriptions
when (signalStrength) {
// No signal
- 0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
+ 0 ->
+ Icon.Resource(
+ res = R.drawable.ic_satellite_connected_0,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_no_connection
+ )
+ )
// Poor -> Moderate
1,
- 2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
+ 2 ->
+ Icon.Resource(
+ res = R.drawable.ic_satellite_connected_1,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_poor_connection
+ )
+ )
// Good -> Great
3,
- 4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
+ 4 ->
+ Icon.Resource(
+ res = R.drawable.ic_satellite_connected_2,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_good_connection
+ )
+ )
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index b598782..65c2e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -47,13 +47,13 @@
import android.view.WindowManager;
import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.res.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -188,23 +188,23 @@
* updated animation controller that handles status-bar-related animation details. Returns an
* empty optional if the animation is *not* on a view in the status bar.
*/
- public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar(
- View rootView, ActivityLaunchAnimator.Controller animationController) {
+ public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
+ View rootView, ActivityTransitionAnimator.Controller animationController) {
if (rootView != mStatusBarWindowView) {
return Optional.empty();
}
- animationController.setLaunchContainer(mLaunchAnimationContainer);
+ animationController.setTransitionContainer(mLaunchAnimationContainer);
return Optional.of(new DelegateLaunchAnimatorController(animationController) {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
- getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
+ public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
setLaunchAnimationRunning(true);
}
@Override
- public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
- getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
+ public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
setLaunchAnimationRunning(false);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 3376e23..147e158 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.theme;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
+
import android.annotation.AnyThread;
import android.content.om.FabricatedOverlay;
import android.content.om.OverlayIdentifier;
@@ -32,6 +34,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.google.android.collect.Lists;
@@ -142,6 +145,7 @@
private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
private final OverlayManager mOverlayManager;
private final Executor mBgExecutor;
+ private final Executor mMainExecutor;
private final String mLauncherPackage;
private final String mThemePickerPackage;
@@ -150,9 +154,11 @@
@Background Executor bgExecutor,
@Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage,
@Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Main Executor mainExecutor) {
mOverlayManager = overlayManager;
mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -184,12 +190,21 @@
/**
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
+ *
+ * @param categoryToPackage Overlay packages to be applied
+ * @param pendingCreation Overlays yet to be created
+ * @param currentUser Current User ID
+ * @param managedProfiles Profiles get overlays
+ * @param onComplete Callback for when resources are ready. Runs in the main thread.
*/
public void applyCurrentUserOverlays(
Map<String, OverlayIdentifier> categoryToPackage,
FabricatedOverlay[] pendingCreation,
int currentUser,
- Set<UserHandle> managedProfiles) {
+ Set<UserHandle> managedProfiles,
+ Runnable onComplete
+ ) {
+
mBgExecutor.execute(() -> {
// Disable all overlays that have not been specified in the user setting.
@@ -236,6 +251,10 @@
try {
mOverlayManager.commit(transaction.build());
+ if (enableHomeDelay() && onComplete != null) {
+ Log.d(TAG, "Executing onComplete runnable");
+ mMainExecutor.execute(onComplete);
+ }
} catch (SecurityException | IllegalStateException e) {
Log.e(TAG, "setEnabled failed", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..585ab72 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -20,6 +20,7 @@
import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -31,6 +32,7 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
+import android.app.ActivityManager;
import android.app.UiModeManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
@@ -140,6 +142,7 @@
// Current wallpaper colors associated to a user.
private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
private final WallpaperManager mWallpaperManager;
+ private final ActivityManager mActivityManager;
@VisibleForTesting
protected ColorScheme mColorScheme;
// If fabricated overlays were already created for the current theme.
@@ -414,7 +417,8 @@
WakefulnessLifecycle wakefulnessLifecycle,
JavaAdapter javaAdapter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- UiModeManager uiModeManager) {
+ UiModeManager uiModeManager,
+ ActivityManager activityManager) {
mContext = context;
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
@@ -433,6 +437,7 @@
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mUiModeManager = uiModeManager;
+ mActivityManager = activityManager;
dumpManager.registerDumpable(TAG, this);
}
@@ -806,8 +811,16 @@
}
}
+ final Runnable onCompleteCallback = !enableHomeDelay()
+ ? () -> {}
+ : () -> {
+ Log.d(TAG, "ThemeHomeDelay: ThemeOverlayController ready");
+ mActivityManager.setThemeOverlayReady(true);
+ };
+
if (colorSchemeIsApplied(managedProfiles)) {
Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme);
+ onCompleteCallback.run();
return;
}
@@ -816,15 +829,19 @@
.map(key -> key + " -> " + categoryToPackage.get(key)).collect(
Collectors.joining(", ")));
}
+
+ FabricatedOverlay[] fOverlays = null;
+
if (mNeedsOverlayCreation) {
mNeedsOverlayCreation = false;
- mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{
+ fOverlays = new FabricatedOverlay[]{
mSecondaryOverlay, mNeutralOverlay, mDynamicOverlay
- }, currentUser, managedProfiles);
- } else {
- mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
- managedProfiles);
+ };
}
+
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, fOverlays, currentUser,
+ managedProfiles, onCompleteCallback);
+
}
private Style fetchThemeStyleFromSetting() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
new file mode 100644
index 0000000..5c53ff9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.unfold
+
+import android.animation.ValueAnimator
+import android.annotation.BinderThread
+import android.content.Context
+import android.os.Handler
+import android.os.SystemProperties
+import android.util.Log
+import android.view.animation.DecelerateInterpolator
+import androidx.core.animation.addListener
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.statusbar.LinearSideLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
+
+class FoldLightRevealOverlayAnimation
+@Inject
+constructor(
+ private val context: Context,
+ @UnfoldBg private val bgHandler: Handler,
+ private val deviceStateRepository: DeviceStateRepository,
+ private val powerInteractor: PowerInteractor,
+ @Background private val applicationScope: CoroutineScope,
+ private val animationStatusRepository: AnimationStatusRepository,
+ private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
+
+ private val revealProgressValueAnimator: ValueAnimator =
+ ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+ private lateinit var controller: FullscreenLightRevealAnimationController
+ @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
+
+ override fun init() {
+ // This method will be called only on devices where this animation is enabled,
+ // so normally this thread won't be created
+ if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+ return
+ }
+
+ controller =
+ controllerFactory.create(
+ displaySelector = { minByOrNull { it.naturalWidth } },
+ effectFactory = { LinearSideLightRevealEffect(it.isVerticalRotation()) },
+ overlayContainerName = SURFACE_CONTAINER_NAME
+ )
+ controller.init()
+
+ applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ powerInteractor.screenPowerState.collect {
+ if (it == ScreenPowerState.SCREEN_ON) {
+ readyCallback = null
+ }
+ }
+ }
+
+ applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ deviceStateRepository.state
+ .map { it != DeviceStateRepository.DeviceState.FOLDED }
+ .distinctUntilChanged()
+ .filter { isUnfolded -> isUnfolded }
+ .collect { controller.ensureOverlayRemoved() }
+ }
+
+ applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ deviceStateRepository.state
+ .filter {
+ animationStatusRepository.areAnimationsEnabled().first() &&
+ it == DeviceStateRepository.DeviceState.FOLDED
+ }
+ .collect {
+ try {
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
+ playFoldLightRevealOverlayAnimation()
+ }
+ } catch (e: TimeoutCancellationException) {
+ Log.e(TAG, "Fold light reveal animation timed out")
+ ensureOverlayRemovedInternal()
+ }
+ }
+ }
+ }
+
+ @BinderThread
+ override fun onScreenTurningOn(onOverlayReady: Runnable) {
+ readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
+ }
+
+ private suspend fun waitForScreenTurnedOn() {
+ powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ }
+
+ private fun ensureOverlayRemovedInternal() {
+ revealProgressValueAnimator.cancel()
+ controller.ensureOverlayRemoved()
+ }
+
+ private fun playFoldLightRevealOverlayAnimation() {
+ revealProgressValueAnimator.duration = ANIMATION_DURATION
+ revealProgressValueAnimator.interpolator = DecelerateInterpolator()
+ revealProgressValueAnimator.addUpdateListener { animation ->
+ controller.updateRevealAmount(animation.animatedFraction)
+ }
+ revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
+ revealProgressValueAnimator.start()
+ }
+
+ private companion object {
+ const val TAG = "FoldLightRevealOverlayAnimation"
+ const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+ const val SURFACE_CONTAINER_NAME = "fold-overlay-container"
+ val ANIMATION_DURATION: Long
+ get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
new file mode 100644
index 0000000..668b143
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.unfold
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.os.Looper
+import android.os.Trace
+import android.view.Choreographer
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.util.concurrency.ThreadFactory
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.lang.IllegalArgumentException
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.launch
+
+interface FullscreenLightRevealAnimation {
+ fun init()
+
+ fun onScreenTurningOn(onOverlayReady: Runnable)
+}
+
+class FullscreenLightRevealAnimationController
+@AssistedInject
+constructor(
+ private val context: Context,
+ private val displayManager: DisplayManager,
+ private val threadFactory: ThreadFactory,
+ @UnfoldBg private val bgHandler: Handler,
+ @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
+ private val displayAreaHelper: Optional<DisplayAreaHelper>,
+ private val displayTracker: DisplayTracker,
+ @Background private val applicationScope: CoroutineScope,
+ @Main private val executor: Executor,
+ @Assisted private val displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+ @Assisted private val lightRevealEffectFactory: (rotation: Int) -> LightRevealEffect,
+ @Assisted private val overlayContainerName: String
+) {
+
+ private lateinit var bgExecutor: Executor
+ private lateinit var wwm: WindowlessWindowManager
+
+ private var currentRotation: Int = context.display.rotation
+ private var root: SurfaceControlViewHost? = null
+ private var scrimView: LightRevealScrim? = null
+
+ private val rotationWatcher = RotationWatcher()
+ private val internalDisplayInfos: Sequence<DisplayInfo>
+ get() =
+ displayManager
+ .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+ .asSequence()
+ .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
+ .filter { it.type == Display.TYPE_INTERNAL }
+
+ var isTouchBlocked: Boolean = false
+ set(value) {
+ if (value != field) {
+ traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
+ field = value
+ }
+ }
+
+ fun init() {
+ bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
+ rotationChangeProvider.addCallback(rotationWatcher)
+
+ buildSurface { builder ->
+ applicationScope.launch(executor.asCoroutineDispatcher()) {
+ val overlayContainer = builder.build()
+
+ SurfaceControl.Transaction()
+ .setLayer(overlayContainer, OVERLAY_LAYER_Z_INDEX)
+ .show(overlayContainer)
+ .apply()
+
+ wwm =
+ WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
+ }
+ }
+ }
+
+ fun addOverlay(
+ initialAlpha: Float,
+ onOverlayReady: Runnable? = null,
+ ) {
+ if (!::wwm.isInitialized) {
+ // Surface overlay is not created yet on the first SysUI launch
+ onOverlayReady?.run()
+ return
+ }
+ ensureInBackground()
+ ensureOverlayRemoved()
+ prepareOverlay(onOverlayReady, wwm, bgExecutor, initialAlpha)
+ }
+
+ fun ensureOverlayRemoved() {
+ ensureInBackground()
+
+ traceSection("ensureOverlayRemoved") {
+ root?.release()
+ root = null
+ scrimView = null
+ }
+ }
+
+ fun isOverlayVisible(): Boolean {
+ return scrimView == null
+ }
+
+ fun updateRevealAmount(revealAmount: Float) {
+ scrimView?.revealAmount = revealAmount
+ }
+
+ private fun buildSurface(onUpdated: Consumer<SurfaceControl.Builder>) {
+ val containerBuilder =
+ SurfaceControl.Builder(SurfaceSession())
+ .setContainerLayer()
+ .setName(overlayContainerName)
+
+ displayAreaHelper
+ .get()
+ .attachToRootDisplayArea(displayTracker.defaultDisplayId, containerBuilder, onUpdated)
+ }
+
+ private fun prepareOverlay(
+ onOverlayReady: Runnable? = null,
+ wwm: WindowlessWindowManager,
+ bgExecutor: Executor,
+ initialAlpha: Float,
+ ) {
+ val newRoot = SurfaceControlViewHost(context, context.display, wwm, javaClass.simpleName)
+
+ val params = getLayoutParams()
+ val newView =
+ LightRevealScrim(
+ context,
+ attrs = null,
+ initialWidth = params.width,
+ initialHeight = params.height
+ )
+ .apply {
+ revealEffect = lightRevealEffectFactory(currentRotation)
+ revealAmount = initialAlpha
+ }
+
+ newRoot.setView(newView, params)
+
+ if (onOverlayReady != null) {
+ Trace.beginAsyncSection("$TAG#relayout", 0)
+
+ newRoot.relayout(params) { transaction ->
+ val vsyncId = Choreographer.getSfInstance().vsyncId
+ transaction.setFrameTimelineVsync(vsyncId).apply()
+
+ transaction
+ .setFrameTimelineVsync(vsyncId + 1)
+ .addTransactionCommittedListener(bgExecutor) {
+ Trace.endAsyncSection("$TAG#relayout", 0)
+ onOverlayReady.run()
+ }
+ .apply()
+ }
+ }
+ root = newRoot
+ scrimView = newView
+ }
+
+ private fun ensureInBackground() {
+ check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+ }
+
+ private fun getLayoutParams(): WindowManager.LayoutParams {
+ val displayInfo =
+ internalDisplayInfos.displaySelector()
+ ?: throw IllegalArgumentException("No internal displays found!")
+ return WindowManager.LayoutParams().apply {
+ if (currentRotation.isVerticalRotation()) {
+ height = displayInfo.naturalHeight
+ width = displayInfo.naturalWidth
+ } else {
+ height = displayInfo.naturalWidth
+ width = displayInfo.naturalHeight
+ }
+ format = PixelFormat.TRANSLUCENT
+ type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+ title = javaClass.simpleName
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ fitInsetsTypes = 0
+
+ flags =
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ setTrustedOverlay()
+
+ packageName = context.opPackageName
+ }
+ }
+
+ private inner class RotationWatcher : RotationChangeProvider.RotationListener {
+ override fun onRotationChanged(newRotation: Int) {
+ traceSection("$TAG#onRotationChanged") {
+ if (currentRotation != newRotation) {
+ currentRotation = newRotation
+ scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
+ root?.relayout(getLayoutParams())
+ }
+ }
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+ effectFactory: (rotation: Int) -> LightRevealEffect,
+ overlayContainerName: String
+ ): FullscreenLightRevealAnimationController
+ }
+
+ companion object {
+ private const val TAG = "FullscreenLightRevealAnimation"
+ private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+ private const val OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+ const val ALPHA_TRANSPARENT = 1f
+ const val ALPHA_OPAQUE = 0f
+
+ fun @receiver:Rotation Int.isVerticalRotation(): Boolean =
+ this == Surface.ROTATION_0 || this == Surface.ROTATION_180
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 0016d95..139ac7e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.unfold
import com.android.keyguard.KeyguardUnfoldTransition
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
@@ -25,10 +26,14 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
import dagger.BindsInstance
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Named
import javax.inject.Scope
@@ -70,8 +75,33 @@
}
}
+@Module
+interface SysUIUnfoldStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(UnfoldInitializationStartable::class)
+ fun bindsUnfoldInitializationStartable(impl: UnfoldInitializationStartable): CoreStartable
+}
+
+@Module
+abstract class SysUIUnfoldInternalModule {
+ @Binds
+ @IntoSet
+ @SysUIUnfoldScope
+ abstract fun bindsUnfoldLightRevealOverlayAnimation(
+ anim: UnfoldLightRevealOverlayAnimation
+ ): FullscreenLightRevealAnimation
+
+ @Binds
+ @IntoSet
+ @SysUIUnfoldScope
+ abstract fun bindsFoldLightRevealOverlayAnimation(
+ anim: FoldLightRevealOverlayAnimation
+ ): FullscreenLightRevealAnimation
+}
+
@SysUIUnfoldScope
-@Subcomponent
+@Subcomponent(modules = [SysUIUnfoldInternalModule::class])
interface SysUIUnfoldComponent {
@Subcomponent.Factory
@@ -92,12 +122,12 @@
fun getFoldAodAnimationController(): FoldAodAnimationController
+ fun getFullScreenLightRevealAnimations(): Set<FullscreenLightRevealAnimation>
+
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
- fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
-
fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager
fun getUnfoldLatencyTracker(): UnfoldLatencyTracker
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt
new file mode 100644
index 0000000..75d8a58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.unfold
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
+import java.util.Optional
+import javax.inject.Inject
+
+class UnfoldInitializationStartable
+@Inject
+constructor(
+ private val unfoldComponentOptional: Optional<SysUIUnfoldComponent>,
+ private val foldStateLoggingProviderOptional: Optional<FoldStateLoggingProvider>,
+ private val foldStateLoggerOptional: Optional<FoldStateLogger>,
+ @UnfoldBg
+ private val unfoldBgTransitionProgressProviderOptional:
+ Optional<UnfoldTransitionProgressProvider>,
+ private val unfoldTransitionProgressProviderOptional:
+ Optional<UnfoldTransitionProgressProvider>,
+ private val unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder>
+) : CoreStartable {
+ override fun start() {
+ unfoldComponentOptional.ifPresent { c: SysUIUnfoldComponent ->
+ c.getFullScreenLightRevealAnimations().forEach { it: FullscreenLightRevealAnimation ->
+ it.init()
+ }
+ c.getUnfoldTransitionWallpaperController().init()
+ c.getUnfoldHapticsPlayer()
+ c.getNaturalRotationUnfoldProgressProvider().init()
+ c.getUnfoldLatencyTracker().init()
+ }
+
+ foldStateLoggingProviderOptional.ifPresent { obj: FoldStateLoggingProvider -> obj.init() }
+ foldStateLoggerOptional.ifPresent { obj: FoldStateLogger -> obj.init() }
+
+ val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> =
+ if (Flags.unfoldAnimationBackgroundProgress()) {
+ unfoldBgTransitionProgressProviderOptional
+ } else {
+ unfoldTransitionProgressProviderOptional
+ }
+ unfoldTransitionProgressProvider.ifPresent {
+ progressProvider: UnfoldTransitionProgressProvider ->
+ unfoldTransitionProgressForwarder.ifPresent {
+ listener: UnfoldTransitionProgressForwarder ->
+ progressProvider.addCallback(listener)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index b72c6f1..f355dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -18,42 +18,22 @@
import android.annotation.BinderThread
import android.content.ContentResolver
import android.content.Context
-import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
-import android.hardware.devicestate.DeviceStateManager.FoldStateListener
-import android.hardware.display.DisplayManager
import android.hardware.input.InputManagerGlobal
import android.os.Handler
-import android.os.Looper
import android.os.Trace
-import android.view.Choreographer
-import android.view.Display
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.SurfaceSession
-import android.view.WindowManager
-import android.view.WindowlessWindowManager
-import com.android.app.tracing.traceSection
-import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.LightRevealEffect
-import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.dagger.UnfoldBg
-import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.wm.shell.displayareahelper.DisplayAreaHelper
-import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
@@ -64,80 +44,43 @@
@Inject
constructor(
private val context: Context,
- private val featureFlags: FeatureFlags,
- private val deviceStateManager: DeviceStateManager,
+ private val featureFlags: FeatureFlagsClassic,
private val contentResolver: ContentResolver,
- private val displayManager: DisplayManager,
+ @UnfoldBg private val unfoldProgressHandler: Handler,
@UnfoldBg
private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
- private val displayAreaHelper: Optional<DisplayAreaHelper>,
- @Main private val executor: Executor,
+ private val deviceStateManager: DeviceStateManager,
private val threadFactory: ThreadFactory,
- @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
- @UnfoldBg private val unfoldProgressHandler: Handler,
- private val displayTracker: DisplayTracker,
- private val scrimLogger: ScrimLogger,
-) {
+ private val fullscreenLightRevealAnimationControllerFactory:
+ FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
private val transitionListener = TransitionListener()
- private val rotationWatcher = RotationWatcher()
-
- private lateinit var bgHandler: Handler
- private lateinit var bgExecutor: Executor
-
- private lateinit var wwm: WindowlessWindowManager
- private lateinit var unfoldedDisplayInfo: DisplayInfo
- private lateinit var overlayContainer: SurfaceControl
-
- private var root: SurfaceControlViewHost? = null
- private var scrimView: LightRevealScrim? = null
private var isFolded: Boolean = false
private var isUnfoldHandled: Boolean = true
- private var overlayAddReason: AddOverlayReason? = null
- private var isTouchBlocked: Boolean = true
+ private var overlayAddReason: AddOverlayReason = UNFOLD
+ private lateinit var controller: FullscreenLightRevealAnimationController
+ private lateinit var bgExecutor: Executor
- private var currentRotation: Int = context.display!!.rotation
-
- fun init() {
+ override fun init() {
// This method will be called only on devices where this animation is enabled,
// so normally this thread won't be created
- bgHandler = unfoldProgressHandler
- bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
+ controller =
+ fullscreenLightRevealAnimationControllerFactory.create(
+ displaySelector = { maxByOrNull { it.naturalWidth } },
+ effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) },
+ overlayContainerName = SURFACE_CONTAINER_NAME,
+ )
+ controller.init()
+ bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler)
deviceStateManager.registerCallback(bgExecutor, FoldListener())
if (unfoldAnimationBackgroundProgress()) {
unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
} else {
unfoldTransitionProgressProvider.get().addCallback(transitionListener)
}
- rotationChangeProvider.addCallback(rotationWatcher)
-
- val containerBuilder =
- SurfaceControl.Builder(SurfaceSession())
- .setContainerLayer()
- .setName("unfold-overlay-container")
-
- displayAreaHelper.get().attachToRootDisplayArea(
- displayTracker.defaultDisplayId,
- containerBuilder
- ) { builder ->
- executor.execute {
- overlayContainer = builder.build()
-
- SurfaceControl.Transaction()
- .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
- .show(overlayContainer)
- .apply()
-
- wwm =
- WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
- }
- }
-
- // Get unfolded display size immediately as 'current display info' might be
- // not up-to-date during unfolding
- unfoldedDisplayInfo = getUnfoldedDisplayInfo()
}
/**
@@ -148,17 +91,18 @@
* @see [com.android.systemui.keyguard.KeyguardViewMediator]
*/
@BinderThread
- fun onScreenTurningOn(onOverlayReady: Runnable) {
+ override fun onScreenTurningOn(onOverlayReady: Runnable) {
executeInBackground {
Trace.beginSection("$TAG#onScreenTurningOn")
try {
// Add the view only if we are unfolding and this is the first screen on
if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
- addOverlay(onOverlayReady, reason = UNFOLD)
+ overlayAddReason = UNFOLD
+ controller.addOverlay(calculateRevealAmount(), onOverlayReady)
isUnfoldHandled = true
} else {
// No unfold transition, immediately report that overlay is ready
- ensureOverlayRemoved()
+ controller.ensureOverlayRemoved()
onOverlayReady.run()
}
} finally {
@@ -167,78 +111,15 @@
}
}
- private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
- if (!::wwm.isInitialized) {
- // Surface overlay is not created yet on the first SysUI launch
- onOverlayReady?.run()
- return
- }
-
- ensureInBackground()
- ensureOverlayRemoved()
-
- overlayAddReason = reason
-
- val newRoot =
- SurfaceControlViewHost(
- context,
- context.display,
- wwm,
- "UnfoldLightRevealOverlayAnimation"
- )
- val params = getLayoutParams()
- val newView =
- LightRevealScrim(
- context,
- attrs = null,
- initialWidth = params.width,
- initialHeight = params.height
- )
- .apply {
- revealEffect = createLightRevealEffect()
- revealAmount = calculateRevealAmount()
- scrimLogger = this@UnfoldLightRevealOverlayAnimation.scrimLogger
- }
-
- newRoot.setView(newView, params)
-
- if (onOverlayReady != null) {
- Trace.beginAsyncSection("$TAG#relayout", 0)
-
- newRoot.relayout(params) { transaction ->
- val vsyncId = Choreographer.getSfInstance().vsyncId
-
- // Apply the transaction that contains the first frame of the overlay and apply
- // another empty transaction with 'vsyncId + 1' to make sure that it is actually
- // displayed on the screen. The second transaction is necessary to remove the screen
- // blocker (turn on the brightness) only when the content is actually visible as it
- // might be presented only in the next frame.
- // See b/197538198
- transaction.setFrameTimelineVsync(vsyncId).apply()
-
- transaction
- .setFrameTimelineVsync(vsyncId + 1)
- .addTransactionCommittedListener(bgExecutor) {
- Trace.endAsyncSection("$TAG#relayout", 0)
- onOverlayReady.run()
- }
- .apply()
- }
- }
-
- scrimView = newView
- root = newRoot
- }
-
private fun calculateRevealAmount(animationProgress: Float? = null): Float {
- val overlayAddReason = overlayAddReason ?: UNFOLD
+ val overlayAddReason = overlayAddReason
if (animationProgress == null) {
- // Animation progress is unknown, calculate the initial value based on the overlay
+ // Animation progress unknown, calculate the initial value based on the overlay
// add reason
return when (overlayAddReason) {
- FOLD -> TRANSPARENT
- UNFOLD -> BLACK
+ FOLD -> ALPHA_TRANSPARENT
+ UNFOLD -> ALPHA_OPAQUE
}
}
@@ -249,144 +130,57 @@
// Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
// and we are folding the device. We still add the overlay to block touches
// while the animation is running but the overlay is transparent.
- TRANSPARENT
+ ALPHA_TRANSPARENT
} else {
animationProgress
}
}
- private fun getLayoutParams(): WindowManager.LayoutParams {
- val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
+ private inner class TransitionListener :
+ UnfoldTransitionProgressProvider.TransitionProgressListener {
- val rotation = currentRotation
- val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
- params.height =
- if (isNatural) unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
- params.width =
- if (isNatural) unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
-
- params.format = PixelFormat.TRANSLUCENT
- params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
- params.title = "Unfold Light Reveal Animation"
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- params.fitInsetsTypes = 0
-
- val touchFlags =
- if (isTouchBlocked) {
- // Touchable by default, so it will block the touches
- 0
- } else {
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- }
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or touchFlags
- params.setTrustedOverlay()
-
- val packageName: String = context.opPackageName
- params.packageName = packageName
-
- return params
- }
-
- private fun updateTouchBlockIfNeeded(progress: Float) {
- // When unfolding unblock touches a bit earlier than the animation end as the
- // interpolation has a long tail of very slight movement at the end which should not
- // affect much the usage of the device
- val shouldBlockTouches =
- if (overlayAddReason == UNFOLD) {
- progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
- } else {
- true
- }
-
- if (isTouchBlocked != shouldBlockTouches) {
- isTouchBlocked = shouldBlockTouches
-
- traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
- }
- }
-
- private fun createLightRevealEffect(): LightRevealEffect {
- val isVerticalFold =
- currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
- return LinearLightRevealEffect(isVertical = isVerticalFold)
- }
-
- private fun ensureOverlayRemoved() {
- ensureInBackground()
- traceSection("ensureOverlayRemoved") {
- root?.release()
- root = null
- scrimView = null
- }
- }
-
- private fun getUnfoldedDisplayInfo(): DisplayInfo =
- displayManager
- .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
- .asSequence()
- .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
- .filter { it.type == Display.TYPE_INTERNAL }
- .maxByOrNull { it.naturalWidth }!!
-
- private inner class TransitionListener : TransitionProgressListener {
-
- override fun onTransitionProgress(progress: Float) {
- executeInBackground {
- scrimView?.revealAmount = calculateRevealAmount(progress)
- updateTouchBlockIfNeeded(progress)
- }
+ override fun onTransitionProgress(progress: Float) = executeInBackground {
+ controller.updateRevealAmount(calculateRevealAmount(progress))
+ // When unfolding unblock touches a bit earlier than the animation end as the
+ // interpolation has a long tail of very slight movement at the end which should not
+ // affect much the usage of the device
+ controller.isTouchBlocked =
+ overlayAddReason == FOLD || progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
}
- override fun onTransitionFinished() {
- executeInBackground { ensureOverlayRemoved() }
+ override fun onTransitionFinished() = executeInBackground {
+ controller.ensureOverlayRemoved()
}
override fun onTransitionStarted() {
// Add view for folding case (when unfolding the view is added earlier)
- if (scrimView == null) {
- executeInBackground { addOverlay(reason = FOLD) }
+ if (controller.isOverlayVisible()) {
+ executeInBackground {
+ overlayAddReason = FOLD
+ controller.addOverlay(calculateRevealAmount())
+ }
}
// Disable input dispatching during transition.
InputManagerGlobal.getInstance().cancelCurrentTouch()
}
}
- private inner class RotationWatcher : RotationChangeProvider.RotationListener {
- override fun onRotationChanged(newRotation: Int) {
- executeInBackground {
- traceSection("$TAG#onRotationChanged") {
- if (currentRotation != newRotation) {
- currentRotation = newRotation
- scrimView?.revealEffect = createLightRevealEffect()
- root?.relayout(getLayoutParams())
- }
- }
- }
- }
- }
-
private fun executeInBackground(f: () -> Unit) {
// This is needed to allow progresses to be received both from the main thread (that will
// schedule a runnable on the bg thread), and from the bg thread directly (no reposting).
- if (bgHandler.looper.isCurrentThread) {
+ if (unfoldProgressHandler.looper.isCurrentThread) {
f()
} else {
- bgHandler.post(f)
+ unfoldProgressHandler.post(f)
}
}
- private fun ensureInBackground() {
- check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
- }
-
private inner class FoldListener :
- FoldStateListener(
+ DeviceStateManager.FoldStateListener(
context,
Consumer { isFolded ->
if (isFolded) {
- ensureOverlayRemoved()
+ controller.ensureOverlayRemoved()
isUnfoldHandled = false
}
this.isFolded = isFolded
@@ -400,16 +194,7 @@
private companion object {
const val TAG = "UnfoldLightRevealOverlayAnimation"
- const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
-
- // Put the unfold overlay below the rotation animation screenshot to hide the moment
- // when it is rotated but the rotation of the other windows hasn't happen yet
- const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
-
- // constants for revealAmount.
- const val TRANSPARENT = 1f
- const val BLACK = 0f
-
- private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
+ const val SURFACE_CONTAINER_NAME = "unfold-overlay-container"
+ const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 0fb4b43..38b381a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -1,6 +1,7 @@
package com.android.systemui.user.domain.interactor
import android.annotation.UserIdInt
+import android.content.pm.UserInfo
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags.refactorGetCurrentUser
import com.android.systemui.dagger.SysUISingleton
@@ -16,6 +17,9 @@
/** Flow providing the ID of the currently selected user. */
val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+ /** Flow providing the [UserInfo] of the currently selected user. */
+ val selectedUserInfo = repository.selectedUserInfo
+
/**
* Returns the ID of the currently-selected user.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index c170eb5..a122311 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -27,7 +27,6 @@
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
-import android.os.Process
import android.os.RemoteException
import android.os.UserHandle
import android.os.UserManager
@@ -50,6 +49,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapper
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -108,6 +108,7 @@
private val guestUserInteractor: GuestUserInteractor,
private val uiEventLogger: UiEventLogger,
private val userRestrictionChecker: UserRestrictionChecker,
+ private val processWrapper: ProcessWrapper
) {
/**
* Defines interface for classes that can be notified when the state of users on the device is
@@ -669,7 +670,7 @@
// Connect to the new secondary user's service (purely to ensure that a persistent
// SystemUI application is created for that user)
- if (userId != Process.myUserHandle().identifier) {
+ if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
applicationContext.startServiceAsUser(
intent,
UserHandle.of(userId),
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
deleted file mode 100644
index d3653b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.view
-
-import android.view.View
-import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
- * updates. New emissions lead to the previous binding call being cancelled if not completed.
- * Dispose of the [DisposableHandle] returned by [bind] when done.
- */
-suspend fun <T : View> Flow<T>.bindLatest(bind: (T) -> DisposableHandle?) {
- this.collectLatest { view ->
- val disposableHandle = bind(view)
- disposableHandle?.awaitCancellationThenDispose()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index ce6d740..90c5c62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -116,9 +116,8 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -145,9 +144,6 @@
import java.util.List;
import java.util.function.Consumer;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
/**
* Visual presentation of the volume dialog.
*
@@ -311,8 +307,6 @@
private int mOrientation;
private final Lazy<SecureSettings> mSecureSettings;
private int mDialogTimeoutMillis;
- private final CoroutineDispatcher mMainDispatcher;
- private final CoroutineScope mApplicationScope;
private final VibratorHelper mVibratorHelper;
private final com.android.systemui.util.time.SystemClock mSystemClock;
@@ -333,14 +327,10 @@
DumpManager dumpManager,
Lazy<SecureSettings> secureSettings,
VibratorHelper vibratorHelper,
- @Main CoroutineDispatcher mainDispatcher,
- @Application CoroutineScope applicationScope,
com.android.systemui.util.time.SystemClock systemClock) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
- mMainDispatcher = mainDispatcher;
- mApplicationScope = applicationScope;
mVibratorHelper = vibratorHelper;
mSystemClock = systemClock;
mShouldListenForJank = shouldListenForJank;
@@ -858,7 +848,10 @@
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
row.slider = row.view.findViewById(R.id.volume_row_slider);
- row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope);
+ if (hapticVolumeSlider()) {
+ row.createPlugin(mVibratorHelper, mSystemClock);
+ HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
+ }
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
@@ -1498,7 +1491,7 @@
for (int i = 0; i < mRows.size(); i++) {
VolumeRow row = mRows.get(i);
if (row.slider.getVisibility() == VISIBLE) {
- row.addHaptics();
+ row.addTouchListener();
}
}
Trace.endSection();
@@ -2620,17 +2613,13 @@
void createPlugin(
VibratorHelper vibratorHelper,
- com.android.systemui.util.time.SystemClock systemClock,
- CoroutineDispatcher mainDispatcher,
- CoroutineScope applicationScope) {
- if (!hapticVolumeSlider() || mHapticPlugin != null) return;
+ com.android.systemui.util.time.SystemClock systemClock) {
+ if (mHapticPlugin != null) return;
mHapticPlugin = new SeekableSliderHapticPlugin(
- vibratorHelper,
- systemClock,
- mainDispatcher,
- applicationScope,
- sSliderHapticFeedbackConfig);
+ vibratorHelper,
+ systemClock,
+ sSliderHapticFeedbackConfig);
}
@@ -2647,19 +2636,9 @@
});
}
- void addHaptics() {
- if (mHapticPlugin != null) {
- addTouchListener();
- mHapticPlugin.start();
- }
- }
-
@SuppressLint("ClickableViewAccessibility")
void removeHaptics() {
slider.setOnTouchListener(null);
- if (mHapticPlugin != null) {
- mHapticPlugin.stop();
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index ff1daea..1af5c46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -18,9 +18,14 @@
import android.content.Context
import android.media.AudioManager
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiverImpl
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
@@ -35,16 +40,33 @@
companion object {
@Provides
- fun provideAudioRepository(
+ fun provideAudioManagerIntentsReceiver(
@Application context: Context,
+ @Application coroutineScope: CoroutineScope,
+ ): AudioManagerIntentsReceiver = AudioManagerIntentsReceiverImpl(context, coroutineScope)
+
+ @Provides
+ fun provideAudioRepository(
+ intentsReceiver: AudioManagerIntentsReceiver,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
): AudioRepository =
- AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
+ AudioRepositoryImpl(intentsReceiver, audioManager, coroutineContext, coroutineScope)
@Provides
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
AudioModeInteractor(repository)
+
+ @Provides
+ fun provdieSpatializerRepository(
+ audioManager: AudioManager,
+ @Background backgroundContext: CoroutineContext,
+ ): SpatializerRepository =
+ SpatializerRepositoryImpl(audioManager.spatializer, backgroundContext)
+
+ @Provides
+ fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
+ SpatializerInteractor(repository)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 2ff9af9..ab76d45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -16,11 +16,11 @@
package com.android.systemui.volume.dagger
-import android.content.Context
import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -37,14 +37,14 @@
@Provides
@SysUISingleton
fun provideMediaDeviceSessionRepository(
- @Application context: Context,
+ intentsReceiver: AudioManagerIntentsReceiver,
mediaSessionManager: MediaSessionManager,
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
@Background backgroundContext: CoroutineContext,
): MediaControllerRepository =
MediaControllerRepositoryImpl(
- context,
+ intentsReceiver,
mediaSessionManager,
localBluetoothManager,
coroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 2718839..3285637 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -23,8 +23,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.VolumeDialog;
@@ -55,9 +53,6 @@
import dagger.multibindings.IntoMap;
import dagger.multibindings.IntoSet;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
/** Dagger Module for code in the volume package. */
@Module(
includes = {
@@ -112,8 +107,6 @@
DumpManager dumpManager,
Lazy<SecureSettings> secureSettings,
VibratorHelper vibratorHelper,
- @Main CoroutineDispatcher mainDispatcher,
- @Application CoroutineScope applicationScope,
SystemClock systemClock) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
@@ -132,8 +125,6 @@
dumpManager,
secureSettings,
vibratorHelper,
- mainDispatcher,
- applicationScope,
systemClock);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 57ac435..0a1ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -15,8 +15,10 @@
*/
package com.android.systemui.volume.panel.component.mediaoutput.data.repository
+import android.media.MediaRouter2Manager
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
@@ -27,6 +29,8 @@
class LocalMediaRepositoryFactory
@Inject
constructor(
+ private val intentsReceiver: AudioManagerIntentsReceiver,
+ private val mediaRouter2Manager: MediaRouter2Manager,
private val localMediaManagerFactory: LocalMediaManagerFactory,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
@@ -34,7 +38,9 @@
fun create(packageName: String?): LocalMediaRepository =
LocalMediaRepositoryImpl(
+ intentsReceiver,
localMediaManagerFactory.create(packageName),
+ mediaRouter2Manager,
coroutineScope,
backgroundCoroutineContext,
)
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index e0228d9..1d9b90a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -34,7 +34,7 @@
import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
import android.util.Log;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -236,12 +236,12 @@
* that too is null, then fall back to {@link WalletActivity}.
*
* @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
- * @param animationController an {@link ActivityLaunchAnimator.Controller} to provide a
+ * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
* smooth animation for the activity launch.
* @param hasCard whether the service returns any cards.
*/
public void startQuickAccessUiIntent(ActivityStarter activityStarter,
- ActivityLaunchAnimator.Controller animationController,
+ ActivityTransitionAnimator.Controller animationController,
boolean hasCard) {
mQuickAccessWalletClient.getWalletPendingIntent(mExecutor,
walletPendingIntent -> {
@@ -271,7 +271,7 @@
private void startQuickAccessViaIntent(Intent intent,
boolean hasCard,
ActivityStarter activityStarter,
- ActivityLaunchAnimator.Controller animationController) {
+ ActivityTransitionAnimator.Controller animationController) {
if (hasCard) {
activityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
@@ -285,7 +285,7 @@
private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent,
ActivityStarter activityStarter,
- ActivityLaunchAnimator.Controller animationController) {
+ ActivityTransitionAnimator.Controller animationController) {
activityStarter.postStartActivityDismissingKeyguard(
pendingIntent,
animationController);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index c2efc05..d048cbe 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -28,6 +28,7 @@
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -88,6 +89,7 @@
@Mock private val mEmergencyButtonController: EmergencyButtonController? = null
private val falsingCollector: FalsingCollector = FalsingCollectorFake()
+ private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
@Mock lateinit var postureController: DevicePostureController
@Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
@@ -143,7 +145,7 @@
featureFlags,
mSelectedUserInteractor,
uiEventLogger,
- FakeKeyboardRepository()
+ keyguardKeyboardInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 0959f1b..4a2554e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -80,6 +81,7 @@
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
val fakeFeatureFlags = FakeFeatureFlags()
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
underTest =
@@ -97,7 +99,7 @@
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
- FakeKeyboardRepository()
+ keyguardKeyboardInteractor
)
underTest.init()
underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 1281e44..4f46184 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,6 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -75,6 +76,7 @@
simPukView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
as KeyguardSimPukView
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
val fakeFeatureFlags = FakeFeatureFlags()
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
underTest =
@@ -92,7 +94,7 @@
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
- FakeKeyboardRepository()
+ keyguardKeyboardInteractor
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index b45c894..fb649c8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -24,8 +24,8 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.FoldAodAnimationController
+import com.android.systemui.unfold.FullscreenLightRevealAnimation
import com.android.systemui.unfold.SysUIUnfoldComponent
-import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation
import com.android.systemui.util.mockito.capture
import com.android.systemui.utils.os.FakeHandler
import com.android.systemui.utils.os.FakeHandler.Mode.QUEUEING
@@ -53,7 +53,9 @@
@Mock
private lateinit var foldAodAnimationController: FoldAodAnimationController
@Mock
- private lateinit var unfoldAnimation: UnfoldLightRevealOverlayAnimation
+ private lateinit var fullscreenLightRevealAnimation: FullscreenLightRevealAnimation
+ @Mock
+ private lateinit var fullScreenLightRevealAnimations: Set<FullscreenLightRevealAnimation>
@Captor
private lateinit var readyCaptor: ArgumentCaptor<Runnable>
@@ -67,9 +69,9 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- `when`(unfoldComponent.getUnfoldLightRevealOverlayAnimation())
- .thenReturn(unfoldAnimation)
+ fullScreenLightRevealAnimations = setOf(fullscreenLightRevealAnimation)
+ `when`(unfoldComponent.getFullScreenLightRevealAnimations())
+ .thenReturn(fullScreenLightRevealAnimations)
`when`(unfoldComponent.getFoldAodAnimationController())
.thenReturn(foldAodAnimationController)
@@ -164,7 +166,7 @@
}
private fun onUnfoldOverlayReady() {
- verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor))
+ verify(fullscreenLightRevealAnimation).onScreenTurningOn(capture(readyCaptor))
readyCaptor.value.run()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
index 202d9ce..e157fc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
@@ -91,7 +91,7 @@
whenever(sysuiComponent.startables)
.thenReturn(mutableMapOf(StartableA::class.java to Provider { startableA }))
app.onCreate()
- app.startServicesIfNeeded()
+ app.startSystemUserServicesIfNeeded()
assertThat(startableA.started).isTrue()
}
@@ -105,7 +105,7 @@
)
)
app.onCreate()
- app.startServicesIfNeeded()
+ app.startSystemUserServicesIfNeeded()
assertThat(startableA.started).isTrue()
assertThat(startableB.started).isTrue()
}
@@ -121,7 +121,7 @@
)
)
app.onCreate()
- app.startServicesIfNeeded()
+ app.startSystemUserServicesIfNeeded()
assertThat(startableA.started).isTrue()
assertThat(startableB.started).isTrue()
assertThat(startableC.started).isTrue()
@@ -141,7 +141,7 @@
)
)
app.onCreate()
- app.startServicesIfNeeded()
+ app.startSystemUserServicesIfNeeded()
assertThat(startableA.started).isTrue()
assertThat(startableB.started).isTrue()
assertThat(startableC.started).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 375ebe8..8299acb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,7 +51,6 @@
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.animation.AccelerateInterpolator;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -80,7 +79,6 @@
@LargeTest
@RunWith(AndroidTestingRunner.class)
-@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 8faf715..75a49d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -44,33 +44,37 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
-class ActivityLaunchAnimatorTest : SysuiTestCase() {
- private val launchContainer = LinearLayout(mContext)
- private val testLaunchAnimator = fakeLaunchAnimator()
- @Mock lateinit var callback: ActivityLaunchAnimator.Callback
- @Mock lateinit var listener: ActivityLaunchAnimator.Listener
- @Spy private val controller = TestLaunchAnimatorController(launchContainer)
+class ActivityTransitionAnimatorTest : SysuiTestCase() {
+ private val transitionContainer = LinearLayout(mContext)
+ private val testTransitionAnimator = fakeTransitionAnimator()
+ @Mock lateinit var callback: ActivityTransitionAnimator.Callback
+ @Mock lateinit var listener: ActivityTransitionAnimator.Listener
+ @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
@Before
fun setup() {
- activityLaunchAnimator =
- ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator, disableWmTimeout = true)
- activityLaunchAnimator.callback = callback
- activityLaunchAnimator.addListener(listener)
+ activityTransitionAnimator =
+ ActivityTransitionAnimator(
+ testTransitionAnimator,
+ testTransitionAnimator,
+ disableWmTimeout = true
+ )
+ activityTransitionAnimator.callback = callback
+ activityTransitionAnimator.addListener(listener)
}
@After
fun tearDown() {
- activityLaunchAnimator.removeListener(listener)
+ activityTransitionAnimator.removeListener(listener)
}
private fun startIntentWithAnimation(
- animator: ActivityLaunchAnimator = this.activityLaunchAnimator,
- controller: ActivityLaunchAnimator.Controller? = this.controller,
+ animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
+ controller: ActivityTransitionAnimator.Controller? = this.controller,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
@@ -134,7 +138,7 @@
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(activityLaunchAnimator) { adapter ->
+ startIntentWithAnimation(activityTransitionAnimator) { adapter ->
animationAdapter = adapter
ActivityManager.START_DELIVERED_TO_TOP
}
@@ -159,50 +163,50 @@
@Test
fun doesNotStartIfAnimationIsCancelled() {
- val runner = activityLaunchAnimator.createRunner(controller)
+ val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationCancelled()
runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
- verify(controller).onLaunchAnimationCancelled()
- verify(controller, never()).onLaunchAnimationStart(anyBoolean())
- verify(listener).onLaunchAnimationCancelled()
- verify(listener, never()).onLaunchAnimationStart()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
assertNull(runner.delegate)
}
@Test
fun cancelsIfNoOpeningWindowIsFound() {
- val runner = activityLaunchAnimator.createRunner(controller)
+ val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
- verify(controller).onLaunchAnimationCancelled()
- verify(controller, never()).onLaunchAnimationStart(anyBoolean())
- verify(listener).onLaunchAnimationCancelled()
- verify(listener, never()).onLaunchAnimationStart()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
assertNull(runner.delegate)
}
@Test
fun startsAnimationIfWindowIsOpening() {
- val runner = activityLaunchAnimator.createRunner(controller)
+ val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
- verify(listener).onLaunchAnimationStart()
- verify(controller).onLaunchAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationStart()
+ verify(controller).onTransitionAnimationStart(anyBoolean())
}
@Test
fun creatingControllerFromNormalViewThrows() {
assertThrows(IllegalArgumentException::class.java) {
- ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+ ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
}
}
@Test
fun disposeRunner_delegateDereferenced() {
- val runner = activityLaunchAnimator.createRunner(controller)
+ val runner = activityTransitionAnimator.createRunner(controller)
assertNotNull(runner.delegate)
runner.dispose()
waitForIdleSync()
@@ -237,13 +241,13 @@
}
/**
- * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
+ * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called
* outside of the main thread.
*/
-private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) :
- ActivityLaunchAnimator.Controller {
+private class TestTransitionAnimatorController(override var transitionContainer: ViewGroup) :
+ ActivityTransitionAnimator.Controller {
override fun createAnimatorState() =
- LaunchAnimator.State(
+ TransitionAnimator.State(
top = 100,
bottom = 200,
left = 300,
@@ -262,23 +266,23 @@
assertOnMainThread()
}
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
assertOnMainThread()
}
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
+ override fun onTransitionAnimationProgress(
+ state: TransitionAnimator.State,
progress: Float,
linearProgress: Float
) {
assertOnMainThread()
}
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
assertOnMainThread()
}
- override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
assertOnMainThread()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 2233e322..a586421 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -129,14 +129,14 @@
// The dialog shouldn't be dismissable during the animation.
runOnMainThreadAndWaitForIdleSync {
- controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
secondDialog.dismiss()
}
assertTrue(secondDialog.isShowing)
// Both dialogs should be dismissed at the end of the animation.
runOnMainThreadAndWaitForIdleSync {
- controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
}
assertFalse(firstDialog.isShowing)
assertFalse(secondDialog.isShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index d1ac0e8..8442a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -32,13 +32,13 @@
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
@Test
fun animatingOrphanViewDoesNotCrash() {
- val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+ val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
controller.onIntentStarted(willAnimate = true)
- controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
- controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
- controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationProgress(state, progress = 0f, linearProgress = 0f)
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 0ba9abe..f490f3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -45,7 +45,7 @@
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -89,7 +89,7 @@
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var qsController: QuickSettingsController
- @Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var windowRootView: WindowRootView
@Mock private lateinit var viewRootImpl: ViewRootImpl
@@ -123,7 +123,7 @@
notificationShadeWindowController,
windowRootViewVisibilityInteractor
)
- .apply { this.setup(qsController, shadeViewController) }
+ .apply { this.setup(qsController, shadeBackActionInteractor) }
}
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -165,19 +165,19 @@
val result = backActionInteractor.onBackRequested()
assertTrue(result)
- verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
verify(statusBarKeyguardViewManager, never()).onBackPressed()
}
@Test
fun testOnBackRequested_closeUserSwitcherIfOpen() {
- whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+ whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
val result = backActionInteractor.onBackRequested()
assertTrue(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -189,7 +189,7 @@
assertFalse(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -290,11 +290,11 @@
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
callback.onBackProgressed(createBackEvent(0.3f))
- verify(shadeViewController, never()).onBackProgressed(0.3f)
+ verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
}
@Test
@@ -305,11 +305,11 @@
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
callback.onBackProgressed(createBackEvent(0.4f))
- verify(shadeViewController).onBackProgressed(0.4f)
+ verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
private fun getBackInvokedCallback(): OnBackInvokedCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
deleted file mode 100644
index 112cec2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.common.ui
-
-import android.content.Context
-import android.testing.AndroidTestingRunner
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.mockito.captureMany
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.verify
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ConfigurationStateTest : SysuiTestCase() {
-
- private val configurationController: ConfigurationController = mock()
- private val layoutInflater = TestLayoutInflater()
- private val backgroundDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(backgroundDispatcher)
-
- val underTest = ConfigurationState(configurationController, context, layoutInflater)
-
- @Test
- fun reinflateAndBindLatest_inflatesWithoutEmission() =
- testScope.runTest {
- var callbackCount = 0
- backgroundScope.launch {
- underTest.reinflateAndBindLatest<View>(
- resource = 0,
- root = null,
- attachToRoot = false,
- backgroundDispatcher,
- ) {
- callbackCount++
- null
- }
- }
-
- // Inflates without an emission
- runCurrent()
- assertThat(layoutInflater.inflationCount).isEqualTo(1)
- assertThat(callbackCount).isEqualTo(1)
- }
-
- @Test
- fun reinflateAndBindLatest_reinflatesOnThemeChanged() =
- testScope.runTest {
- var callbackCount = 0
- backgroundScope.launch {
- underTest.reinflateAndBindLatest<View>(
- resource = 0,
- root = null,
- attachToRoot = false,
- backgroundDispatcher,
- ) {
- callbackCount++
- null
- }
- }
- runCurrent()
-
- val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
- verify(configurationController, atLeastOnce()).addCallback(capture())
- }
-
- listOf(1, 2, 3).forEach { count ->
- assertThat(layoutInflater.inflationCount).isEqualTo(count)
- assertThat(callbackCount).isEqualTo(count)
- configListeners.forEach { it.onThemeChanged() }
- runCurrent()
- }
- }
-
- @Test
- fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() =
- testScope.runTest {
- var callbackCount = 0
- backgroundScope.launch {
- underTest.reinflateAndBindLatest<View>(
- resource = 0,
- root = null,
- attachToRoot = false,
- backgroundDispatcher,
- ) {
- callbackCount++
- null
- }
- }
- runCurrent()
-
- val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
- verify(configurationController, atLeastOnce()).addCallback(capture())
- }
-
- listOf(1, 2, 3).forEach { count ->
- assertThat(layoutInflater.inflationCount).isEqualTo(count)
- assertThat(callbackCount).isEqualTo(count)
- configListeners.forEach { it.onDensityOrFontScaleChanged() }
- runCurrent()
- }
- }
-
- @Test
- fun testReinflateAndBindLatest_disposesOnCancel() =
- testScope.runTest {
- var callbackCount = 0
- var disposed = false
- val job = launch {
- underTest.reinflateAndBindLatest<View>(
- resource = 0,
- root = null,
- attachToRoot = false,
- backgroundDispatcher,
- ) {
- callbackCount++
- DisposableHandle { disposed = true }
- }
- }
-
- runCurrent()
- job.cancelAndJoin()
- assertThat(disposed).isTrue()
- }
-
- inner class TestLayoutInflater : LayoutInflater(context) {
-
- var inflationCount = 0
-
- override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
- inflationCount++
- return View(context)
- }
-
- override fun cloneInContext(p0: Context?): LayoutInflater {
- // not needed for this test
- return this
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index b28d0c8..e30dd35d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -33,8 +33,7 @@
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
-import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -56,10 +55,9 @@
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -68,37 +66,33 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
- val kosmos =
- testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
+ private val kosmos = testKosmos()
+ private val testScope: TestScope = kosmos.testScope
private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
- private val testScope = kosmos.testScope
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val fakeUserRepository = kosmos.fakeUserRepository
private val facePropertyRepository = kosmos.facePropertyRepository
- private val fakeDeviceEntryFingerprintAuthRepository =
- kosmos.fakeDeviceEntryFingerprintAuthRepository
+ private val fakeDeviceEntryFingerprintAuthInteractor =
+ kosmos.deviceEntryFingerprintAuthInteractor
private val powerInteractor = kosmos.powerInteractor
private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
- private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+ private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-
underTest =
SystemUIDeviceEntryFaceAuthInteractor(
mContext,
@@ -110,7 +104,7 @@
keyguardTransitionInteractor,
FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
keyguardUpdateMonitor,
- fakeDeviceEntryFingerprintAuthRepository,
+ fakeDeviceEntryFingerprintAuthInteractor,
fakeUserRepository,
facePropertyRepository,
faceWakeUpTriggersConfig,
@@ -126,10 +120,9 @@
underTest.start()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
- whenever(
- faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
- )
- .thenReturn(true)
+ faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+ setOf(WakeSleepReason.LID.powerManagerWakeReason)
+ )
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -168,10 +161,9 @@
underTest.start()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
- whenever(
- faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
- )
- .thenReturn(true)
+ faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+ setOf(WakeSleepReason.LID.powerManagerWakeReason)
+ )
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -194,10 +186,9 @@
underTest.start()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT)
- whenever(
- faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LIFT)
- )
- .thenReturn(false)
+ faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+ setOf(WakeSleepReason.LID.powerManagerWakeReason)
+ )
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -217,10 +208,9 @@
underTest.start()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
- whenever(
- faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
- )
- .thenReturn(true)
+ faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+ setOf(WakeSleepReason.LID.powerManagerWakeReason)
+ )
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -440,7 +430,45 @@
underTest.start()
fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+ }
+
+ @Test
+ fun faceLockoutStateIsResetWheneverFingerprintIsNotLockedOut() =
+ testScope.runTest {
+ underTest.start()
+ fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+ facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.NONE)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isLockedOut.value).isFalse()
+ }
+
+ @Test
+ fun faceLockoutStateIsSetToUsersLockoutStateWheneverFingerprintIsNotLockedOut() =
+ testScope.runTest {
+ underTest.start()
+ fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+ facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.TIMED)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
runCurrent()
assertThat(faceAuthRepository.isLockedOut.value).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index db04962..796d6d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -20,8 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
@@ -36,6 +36,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class SeekableSliderTrackerTest : SysuiTestCase() {
@@ -51,7 +52,7 @@
@Test
fun initializeSliderTracker_startsTracking() = runTest {
// GIVEN Initialized tracker
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// THEN the tracker job is active
assertThat(mSeekableSliderTracker.isTracking).isTrue()
@@ -61,7 +62,7 @@
fun stopTracking_onAnyState_resetsToIdle() = runTest {
enumValues<SliderState>().forEach {
// GIVEN Initialized tracker
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a state in the state machine
mSeekableSliderTracker.setState(it)
@@ -79,7 +80,7 @@
@Test
fun initializeSliderTracker_isIdle() = runTest {
// GIVEN Initialized tracker
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// THEN The state is idle and the listener is not called to play haptics
assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
@@ -88,7 +89,7 @@
@Test
fun startsTrackingTouch_onIdle_entersWaitState() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a start of tracking touch event
val progress = 0f
@@ -106,7 +107,7 @@
@Test
fun waitCompletes_onWait_movesToHandleAcquired() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT
val progress = 0f
@@ -126,7 +127,7 @@
@Test
fun impreciseTouch_onWait_movesToHandleAcquired() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
// slider
@@ -151,7 +152,7 @@
@Test
fun trackJump_onWait_movesToJumpTrackLocationSelected() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
// slider
@@ -175,7 +176,7 @@
@Test
fun upperBookendSelection_onWait_movesToBookendSelected() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
// slider
@@ -197,7 +198,7 @@
@Test
fun lowerBookendSelection_onWait_movesToBookendSelected() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
// slider
@@ -219,7 +220,7 @@
@Test
fun stopTracking_onWait_whenWaitingJobIsActive_resetsToIdle() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
// slider
@@ -240,7 +241,7 @@
@Test
fun progressChangeByUser_onJumpTrackLocationSelected_movesToDragHandleDragging() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a JUMP_TRACK_LOCATION_SELECTED state
mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -256,7 +257,7 @@
@Test
fun touchRelease_onJumpTrackLocationSelected_movesToIdle() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a JUMP_TRACK_LOCATION_SELECTED state
mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -272,7 +273,7 @@
@Test
fun progressChangeByUser_onJumpBookendSelected_movesToDragHandleDragging() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a JUMP_BOOKEND_SELECTED state
mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -288,7 +289,7 @@
@Test
fun touchRelease_onJumpBookendSelected_movesToIdle() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a JUMP_BOOKEND_SELECTED state
mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -306,7 +307,7 @@
@Test
fun progressChangeByUser_onHandleAcquired_movesToDragHandleDragging() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -325,7 +326,7 @@
@Test
fun touchRelease_onHandleAcquired_movesToIdle() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -344,7 +345,7 @@
@Test
fun progressChangeByUser_onHandleDragging_progressOutsideOfBookends_doesNotChangeState() =
runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a DRAG_HANDLE_DRAGGING state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -366,7 +367,7 @@
fun progressChangeByUser_onHandleDragging_reachesLowerBookend_movesToHandleReachedBookend() =
runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_DRAGGING state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -389,7 +390,7 @@
fun progressChangeByUser_onHandleDragging_reachesUpperBookend_movesToHandleReachedBookend() =
runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_DRAGGING state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -410,7 +411,7 @@
@Test
fun touchRelease_onHandleDragging_movesToIdle() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a DRAG_HANDLE_DRAGGING state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -430,7 +431,7 @@
fun progressChangeByUser_outsideOfBookendRange_onLowerBookend_movesToDragHandleDragging() =
runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -451,7 +452,7 @@
@Test
fun progressChangeByUser_insideOfBookendRange_onLowerBookend_doesNotChangeState() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -473,7 +474,7 @@
fun progressChangeByUser_outsideOfBookendRange_onUpperBookend_movesToDragHandleDragging() =
runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -494,7 +495,7 @@
@Test
fun progressChangeByUser_insideOfBookendRange_onUpperBookend_doesNotChangeState() = runTest {
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -514,7 +515,7 @@
@Test
fun touchRelease_onHandleReachedBookend_movesToIdle() = runTest {
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -531,7 +532,7 @@
@Test
fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
// GIVEN an initialized tracker in the IDLE state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
// GIVEN a progress due to an external source that lands at the middle of the slider
val progress = 0.5f
@@ -550,7 +551,7 @@
fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
// GIVEN an initialized tracker in the IDLE state
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// GIVEN a progress due to an external source that lands at the upper bookend
val progress = config.upperBookendThreshold + 0.01f
@@ -567,7 +568,7 @@
fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
// GIVEN an initialized tracker in the IDLE state
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
// WHEN a progress is recorded due to an external source that lands at the lower bookend
val progress = config.lowerBookendThreshold - 0.01f
@@ -583,7 +584,7 @@
@Test
fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
// WHEN the external stimulus is released
@@ -598,7 +599,7 @@
@Test
fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
// WHEN the slider starts tracking touch
@@ -615,7 +616,7 @@
@Test
fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
// WHEN the slider gets an external progress change
@@ -634,7 +635,7 @@
@Test
fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
// WHEN the external stimulus is released
@@ -649,7 +650,7 @@
@Test
fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
// WHEN the slider starts tracking touch
@@ -665,7 +666,7 @@
@Test
fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
- initTracker(testScheduler)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
// WHEN the slider changes progress programmatically at the middle
@@ -684,7 +685,7 @@
fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
// WHEN the slider reaches the lower bookend programmatically
@@ -702,7 +703,7 @@
fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
// GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
val config = SeekableSliderTrackerConfig()
- initTracker(testScheduler, config)
+ initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
// WHEN the slider reaches the lower bookend programmatically
@@ -718,16 +719,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
- scheduler: TestCoroutineScheduler,
+ scope: CoroutineScope,
config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
) {
mSeekableSliderTracker =
- SeekableSliderTracker(
- sliderStateListener,
- sliderEventProducer,
- UnconfinedTestDispatcher(scheduler),
- config
- )
+ SeekableSliderTracker(sliderStateListener, sliderEventProducer, scope, config)
mSeekableSliderTracker.startTracking()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 14cae0b..2732047 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -86,7 +86,7 @@
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -94,7 +94,6 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -187,7 +186,7 @@
private @Mock ShadeController mShadeController;
private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
- private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
private @Mock ScrimController mScrimController;
private @Mock IActivityTaskManager mActivityTaskManagerService;
private @Mock SysuiColorExtractor mColorExtractor;
@@ -754,7 +753,7 @@
@Test
public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
- mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+ mViewMediator.mOccludeAnimationController.onTransitionAnimationEnd(
false /* isExpandingFullyAbove */);
// Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
@@ -764,7 +763,7 @@
@Test
public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() {
- mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled(
+ mViewMediator.mOccludeAnimationController.onTransitionAnimationCancelled(
null /* newKeyguardOccludedState */);
// Since the updateIsKeyguard call is delayed during the animation, ensure it's called if
@@ -1232,7 +1231,7 @@
mWallpaperRepository,
() -> mShadeController,
() -> mNotificationShadeWindowController,
- () -> mActivityLaunchAnimator,
+ () -> mActivityTransitionAnimator,
() -> mScrimController,
mActivityTaskManagerService,
mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4ae7e3..798c7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -24,7 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -225,7 +225,7 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 4d57670..5b93df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -56,6 +56,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -328,7 +329,7 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
@@ -357,7 +358,7 @@
// WHEN the device begins to dream and the dream is lockscreen hosted
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
@@ -438,7 +439,7 @@
// WHEN the lockscreen hosted dream stops
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
@@ -621,7 +622,7 @@
// WHEN a signal comes that dreaming is enabled
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
// THEN the transition is ignored
verify(transitionRepository, never()).startTransition(any())
@@ -766,7 +767,7 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
@@ -795,7 +796,7 @@
// WHEN the device begins to dream with the lockscreen hosted dream
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
@@ -862,7 +863,7 @@
}
@Test
- fun alternateBoucnerToAod() =
+ fun alternateBouncerToAod() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
bouncerRepository.setAlternateVisible(true)
@@ -878,7 +879,7 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
assertThat(transitionRepository)
.startedTransition(
@@ -909,7 +910,7 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
assertThat(transitionRepository)
.startedTransition(
@@ -937,7 +938,7 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
assertThat(transitionRepository)
.startedTransition(
@@ -973,7 +974,7 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
// THEN a transition to LOCKSCREEN should occur
assertThat(transitionRepository)
@@ -1750,7 +1751,7 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
assertThat(transitionRepository)
.startedTransition(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 2d9d5ed..0e9197e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -181,6 +181,31 @@
}
@Test
+ fun usesOnStepToDoubleValueWithState() =
+ testScope.runTest {
+ val flow =
+ underTest.sharedFlowWithState(
+ duration = 1000.milliseconds,
+ onStep = { it * 2 },
+ )
+ val animationValues by collectLastValue(flow)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.STARTED, 0f))
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 0.6f))
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.2f))
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.6f))
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 2f))
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(animationValues).isEqualTo(StateToValue(TransitionState.FINISHED, null))
+ }
+
+ @Test
fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
testScope.runTest {
val flow =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index 1c9c942..bfa8433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.collect.Range
@@ -57,17 +58,19 @@
// The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+ assertThat(enterFromTopTranslationY)
+ .isEqualTo(StateToValue(TransitionState.STARTED, pixels))
- repository.sendTransitionStep(step(0.4f))
- assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+ repository.sendTransitionStep(step(.55f))
+ assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
repository.sendTransitionStep(step(.85f))
- assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+ assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
// At the end, the translation should be complete and set to zero
repository.sendTransitionStep(step(1f))
- assertThat(enterFromTopTranslationY).isEqualTo(0f)
+ assertThat(enterFromTopTranslationY)
+ .isEqualTo(StateToValue(TransitionState.RUNNING, 0f))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
deleted file mode 100644
index 142862d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtones (RingtonePlayer/NotificationPlayer)
-file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 043dae6..100e579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -24,9 +24,9 @@
import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -86,7 +86,7 @@
fun seekBarGone() {
// WHEN seek bar is disabled
val isEnabled = false
- val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0)
+ val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
observer.onChanged(data)
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
@@ -99,7 +99,7 @@
fun seekBarVisible() {
// WHEN seek bar is enabled
val isEnabled = true
- val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
observer.onChanged(data)
// THEN seek bar is visible and thick
assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -109,7 +109,7 @@
@Test
fun seekBarProgress() {
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
observer.onChanged(data)
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
@@ -123,7 +123,8 @@
fun seekBarDisabledWhenSeekNotAvailable() {
// WHEN seek is not available
val isSeekAvailable = false
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isFalse()
@@ -133,7 +134,8 @@
fun seekBarEnabledWhenSeekNotAvailable() {
// WHEN seek is available
val isSeekAvailable = true
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isTrue()
@@ -144,7 +146,7 @@
// WHEN playing
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is animating
verify(mockSquigglyProgress).animate = true
@@ -155,7 +157,7 @@
// WHEN not playing & not scrubbing
val isPlaying = false
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -166,7 +168,7 @@
// WHEN playing & scrubbing
val isPlaying = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -177,7 +179,7 @@
// WHEN playing & scrubbing
val isPlaying = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -187,7 +189,7 @@
fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
val isEnabled = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -199,7 +201,7 @@
fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
val isEnabled = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -211,7 +213,7 @@
fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
val isEnabled = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -221,8 +223,8 @@
@Test
fun seekBarJumpAnimation() {
- val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000)
- val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000)
+ val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
+ val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
// Set initial position of progress bar
observer.onChanged(data0)
@@ -241,7 +243,7 @@
observer.animationEnabled = false
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable does not animate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index c6cfabc..32b6f38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -72,7 +72,7 @@
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
@@ -110,7 +110,7 @@
@Mock
private DialogLaunchAnimator mDialogLaunchAnimator;
@Mock
- private ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController;
+ private ActivityTransitionAnimator.Controller mActivityTransitionAnimatorController;
@Mock
private NearbyMediaDevicesManager mNearbyMediaDevicesManager;
// Mock
@@ -143,7 +143,7 @@
@Mock
private KeyguardManager mKeyguardManager;
@Mock
- private ActivityLaunchAnimator.Controller mController;
+ private ActivityTransitionAnimator.Controller mController;
@Mock
private PowerExemptionManager mPowerExemptionManager;
@Mock
@@ -1122,7 +1122,7 @@
@Test
public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
- mActivityLaunchAnimatorController);
+ mActivityTransitionAnimatorController);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
mMediaOutputController.mCallback = this.mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
index 301d887..d9453d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
@@ -33,6 +33,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
manager = NearbyMediaDevicesManager(commandQueue, logger)
+ manager.start()
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
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 ae47a7b..33f8f1f 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
@@ -38,7 +38,7 @@
import android.view.View
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.view.LaunchableFrameLayout
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -372,7 +372,7 @@
verify(activityStarter)
.startPendingIntentMaybeDismissingKeyguard(
- eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
+ eq(pi), nullable(), nullable<ActivityTransitionAnimator.Controller>())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 23466cc..720c25a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -26,7 +26,7 @@
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
@@ -127,7 +127,7 @@
.startActivity(
intentCaptor.capture(),
/* dismissShade= */ eq(true),
- nullable() as? ActivityLaunchAnimator.Controller,
+ nullable() as? ActivityTransitionAnimator.Controller,
)
assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 3bf59ca..874368b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -29,7 +29,7 @@
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlInfo
@@ -348,7 +348,7 @@
verify(activityStarter).startActivity(
intentCaptor.capture(),
eq(true) /* dismissShade */,
- nullable(ActivityLaunchAnimator.Controller::class.java),
+ nullable(ActivityTransitionAnimator.Controller::class.java),
eq(true) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
@@ -379,7 +379,7 @@
verify(activityStarter).startActivity(
intentCaptor.capture(),
anyBoolean() /* dismissShade */,
- nullable(ActivityLaunchAnimator.Controller::class.java),
+ nullable(ActivityTransitionAnimator.Controller::class.java),
eq(false) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index d757d71..ab90b9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -24,10 +24,13 @@
import com.android.settingslib.RestrictedLockUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -35,6 +38,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.isNull
@@ -61,7 +65,7 @@
@Mock
private lateinit var listener: ToggleSlider.Listener
@Mock
- private lateinit var mBrightnessSliderHapticPlugin: BrightnessSliderHapticPlugin
+ private lateinit var vibratorHelper: VibratorHelper
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -69,6 +73,7 @@
private lateinit var seekBar: SeekBar
private val uiEventLogger = UiEventLoggerFake()
private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
+ private val systemClock = FakeSystemClock()
private lateinit var mController: BrightnessSliderController
@@ -78,13 +83,14 @@
whenever(mirrorController.toggleSlider).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
+ whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
mController =
BrightnessSliderController(
brightnessSliderView,
mFalsingManager,
uiEventLogger,
- mBrightnessSliderHapticPlugin,
+ SeekableSliderHapticPlugin(vibratorHelper, systemClock),
)
mController.init()
mController.setOnChangedListener(listener)
@@ -100,7 +106,6 @@
mController.onViewAttached()
verify(brightnessSliderView).setOnSeekBarChangeListener(notNull())
- verify(mBrightnessSliderHapticPlugin).start()
}
@Test
@@ -110,7 +115,6 @@
verify(brightnessSliderView).setOnSeekBarChangeListener(isNull())
verify(brightnessSliderView).setOnDispatchTouchEventListener(isNull())
- verify(mBrightnessSliderHapticPlugin).stop()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt
deleted file mode 100644
index 51629b5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings.brightness
-
-import android.view.VelocityTracker
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessSliderHapticPluginImplTest : SysuiTestCase() {
-
- @Mock private lateinit var vibratorHelper: VibratorHelper
- @Mock private lateinit var velocityTracker: VelocityTracker
- @Mock private lateinit var mainDispatcher: CoroutineDispatcher
-
- private val systemClock = FakeSystemClock()
- private val sliderEventProducer = SeekableSliderEventProducer()
-
- private lateinit var plugin: BrightnessSliderHapticPluginImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
- }
-
- @Test
- fun start_beginsTrackingSlider() = runTest {
- createPlugin(UnconfinedTestDispatcher(testScheduler))
- plugin.start()
-
- assertThat(plugin.isTracking).isTrue()
- }
-
- @Test
- fun stop_stopsTrackingSlider() = runTest {
- createPlugin(UnconfinedTestDispatcher(testScheduler))
- // GIVEN that the plugin started the tracking component
- plugin.start()
-
- // WHEN called to stop
- plugin.stop()
-
- // THEN the tracking component stops
- assertThat(plugin.isTracking).isFalse()
- }
-
- @Test
- fun start_afterStop_startsTheTrackingAgain() = runTest {
- createPlugin(UnconfinedTestDispatcher(testScheduler))
- // GIVEN that the plugin started the tracking component
- plugin.start()
-
- // WHEN the plugin is restarted
- plugin.stop()
- plugin.start()
-
- // THEN the tracking begins again
- assertThat(plugin.isTracking).isTrue()
- }
-
- private fun createPlugin(dispatcher: CoroutineDispatcher) {
- plugin =
- BrightnessSliderHapticPluginImpl(
- vibratorHelper,
- systemClock,
- dispatcher,
- velocityTracker,
- sliderEventProducer,
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 9517f82..1dc5f7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -103,8 +103,6 @@
)
testableLooper = TestableLooper.get(this)
- communalRepository.setIsCommunalEnabled(true)
-
whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any()))
.thenReturn(bouncerShowingFlow)
whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow)
@@ -125,36 +123,6 @@
}
@Test
- fun isEnabled_communalEnabled_returnsTrue() {
- communalRepository.setIsCommunalEnabled(true)
-
- assertThat(underTest.isEnabled()).isTrue()
- }
-
- @Test
- fun isEnabled_communalDisabled_returnsFalse() {
- communalRepository.setIsCommunalEnabled(false)
-
- assertThat(underTest.isEnabled()).isFalse()
- }
-
- @Test
- fun initView_notEnabled_throwsException() {
- communalRepository.setIsCommunalEnabled(false)
-
- underTest =
- GlanceableHubContainerController(
- communalInteractor,
- communalViewModel,
- keyguardTransitionInteractor,
- shadeInteractor,
- powerManager,
- )
-
- assertThrows(RuntimeException::class.java) { underTest.initView(context) }
- }
-
- @Test
fun initView_calledTwice_throwsException() {
underTest =
GlanceableHubContainerController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 22b05be..248ed24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -488,7 +488,8 @@
return
}
- whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(true)
+ whenever(mGlanceableHubContainerController.communalAvailable())
+ .thenReturn(MutableStateFlow(true))
val mockCommunalView = mock(View::class.java)
whenever(mGlanceableHubContainerController.initView(any<Context>()))
@@ -513,7 +514,6 @@
return
}
- whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
whenever(mGlanceableHubContainerController.communalAvailable())
.thenReturn(MutableStateFlow(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index cd74410..3315e68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -97,7 +97,7 @@
@Test
fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationIsCancelled() {
flagNotificationAsHun()
- controller.onLaunchAnimationCancelled()
+ controller.onTransitionAnimationCancelled()
assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
@@ -115,7 +115,7 @@
@Test
fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationEnds() {
flagNotificationAsHun()
- controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
@@ -157,7 +157,7 @@
assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
- controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
verify(headsUpManager)
.removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7589a49..354f3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -797,6 +797,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_remoteInput() {
ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
ArgumentCaptor.forClass(RemoteInputController.Callback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 4afcc8c..04f3216 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -440,6 +440,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_noNotifications() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -451,6 +452,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_remoteInput() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -467,6 +469,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_withoutNotifications() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -482,6 +485,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_oneClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -497,6 +501,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_withoutHistory() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -513,6 +518,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(false);
@@ -528,6 +534,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateFooter_oneNonClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -544,9 +551,7 @@
}
@Test
- public void testUpdateFooter_atEnd() {
- mStackScroller.setCurrentUserSetup(true);
-
+ public void testFooterPosition_atEnd() {
// add footer
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -559,8 +564,6 @@
// Expecting the footer to be the last child
int expected = mStackScroller.getChildCount() - 1;
-
- // move footer to end
verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(expected));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 5a57035..dfbe1ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -18,8 +18,10 @@
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
@@ -28,6 +30,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,9 +41,12 @@
import org.mockito.Mockito.verify
private const val VIEW_HEIGHT = 100
+private const val FULL_SHADE_APPEAR_TRANSLATION = 300
+private const val HEADS_UP_ABOVE_SCREEN = 80
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
class StackStateAnimatorTest : SysuiTestCase() {
private lateinit var stackStateAnimator: StackStateAnimator
@@ -51,9 +57,15 @@
private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
@Before
fun setUp() {
+ overrideResource(
+ R.dimen.go_to_full_shade_appearing_translation,
+ FULL_SHADE_APPEAR_TRANSLATION
+ )
+ overrideResource(R.dimen.heads_up_appear_y_above_screen, HEADS_UP_ABOVE_SCREEN)
+
whenever(stackScroller.context).thenReturn(context)
whenever(view.viewState).thenReturn(viewState)
- stackStateAnimator = StackStateAnimator(stackScroller)
+ stackStateAnimator = StackStateAnimator(mContext, stackScroller)
}
@Test
@@ -122,4 +134,22 @@
verify(view, description("should be called at the end of the animation"))
.removeFromTransientContainer()
}
+
+ @Test
+ fun initView_updatesResources() {
+ // Given: the resource values are initialized in the SSA
+ assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation)
+ .isEqualTo(FULL_SHADE_APPEAR_TRANSLATION)
+ assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen)
+ .isEqualTo(HEADS_UP_ABOVE_SCREEN)
+
+ // When: initView is called after the resources have changed
+ overrideResource(R.dimen.go_to_full_shade_appearing_translation, 200)
+ overrideResource(R.dimen.heads_up_appear_y_above_screen, 100)
+ stackStateAnimator.initView(mContext)
+
+ // Then: the resource values are updated
+ assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation).isEqualTo(200)
+ assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen).isEqualTo(100)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 88e4f5a..3a7659d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -27,19 +27,27 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -58,11 +66,16 @@
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
+
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val fakeShadeRepository = kosmos.fakeShadeRepository
- private val zenModeRepository = kosmos.zenModeRepository
private val fakeConfigurationController = kosmos.fakeConfigurationController
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakePowerRepository = kosmos.fakePowerRepository
+ private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+ private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
+ private val zenModeRepository = kosmos.zenModeRepository
val underTest = kosmos.notificationListViewModel
@@ -273,4 +286,193 @@
assertThat(hasFilteredNotifs).isFalse()
}
+
+ @Test
+ fun testShouldShowFooterView_trueWhenShade() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN footer is visible
+ assertThat(shouldShow?.value).isTrue()
+ }
+
+ @Test
+ fun testShouldShowFooterView_trueWhenLockedShade() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open on lockscreen
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN footer is visible
+ assertThat(shouldShow?.value).isTrue()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenKeyguard() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND is on keyguard
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenUserNotSetUp() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ // AND user is not set up
+ fakeUserSetupRepository.setUserSetUp(false)
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenStartingToSleep() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ // AND device is starting to go to sleep
+ fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenQsExpandedDefault() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ // AND quick settings are expanded
+ fakeShadeRepository.setQsExpansion(1f)
+ fakeShadeRepository.legacyQsFullscreen.value = true
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ // AND quick settings are expanded
+ fakeShadeRepository.setQsExpansion(1f)
+ // AND split shade is enabled
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ fakeConfigurationController.notifyConfigurationChanged()
+ runCurrent()
+
+ // THEN footer is visible
+ assertThat(shouldShow?.value).isTrue()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenRemoteInputActive() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ // AND remote input is active
+ fakeRemoteInputRepository.isRemoteInputActive.value = true
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_falseWhenShadeIsClosed() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is closed
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(0f)
+ runCurrent()
+
+ // THEN footer is not visible
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ fun testShouldShowFooterView_animatesWhenShade() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND shade is open and fully expanded
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN footer visibility animates
+ assertThat(shouldShow?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun testShouldShowFooterView_notAnimatingOnKeyguard() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN has notifs
+ activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND we are on the keyguard
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ fakeShadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN footer visibility does not animate
+ assertThat(shouldShow?.isAnimating).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 32c727c..ff882b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -69,7 +69,10 @@
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ }
}
init {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b048949..9c60a2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -83,7 +83,7 @@
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.back.domain.interactor.BackActionInteractor;
import com.android.systemui.biometrics.AuthRippleController;
@@ -307,7 +307,7 @@
@Mock private StartingSurface mStartingSurface;
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
- @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
+ @Mock private ActivityTransitionAnimator mActivityTransitionAnimator;
@Mock private DeviceStateManager mDeviceStateManager;
@Mock private WiredChargingRippleController mWiredChargingRippleController;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@@ -543,7 +543,7 @@
new MessageRouterImpl(mMainExecutor),
mWallpaperManager,
Optional.of(mStartingSurface),
- mActivityLaunchAnimator,
+ mActivityTransitionAnimator,
mDeviceStateManager,
mWiredChargingRippleController,
mDreamManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 597e2e3..41514ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -63,7 +63,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -159,7 +159,7 @@
@Mock
private StatusBarNotificationActivityStarter mNotificationActivityStarter;
@Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private ActivityTransitionAnimator mActivityTransitionAnimator;
@Mock
private InteractionJankMonitor mJankMonitor;
private FakePowerRepository mPowerRepository;
@@ -255,7 +255,7 @@
mock(NotificationPresenter.class),
mock(ShadeViewController.class),
mock(NotificationShadeWindowController.class),
- mActivityLaunchAnimator,
+ mActivityTransitionAnimator,
new ShadeAnimationInteractorLegacyImpl(
new ShadeAnimationRepository(), new FakeShadeRepository()),
notificationAnimationProvider,
@@ -306,7 +306,7 @@
// Then
verify(mShadeController, atLeastOnce()).collapseShade();
- verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
+ verify(mActivityTransitionAnimator).startPendingIntentWithAnimation(any(),
eq(false) /* animate */, any(), any());
verify(mAssistManager).hideAssist();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 83439f0..8f4cbaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -104,9 +104,9 @@
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mManager = new ThemeOverlayApplier(mOverlayManager,
- MoreExecutors.directExecutor(),
- LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+ mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+ LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager,
+ MoreExecutors.directExecutor()) {
@Override
protected OverlayManagerTransaction.Builder getTransactionBuilder() {
return mTransactionBuilder;
@@ -179,7 +179,7 @@
@Test
public void allCategoriesSpecified_allEnabledExclusively() {
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES);
+ TEST_USER_HANDLES, null);
verify(mOverlayManager).commit(any());
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
@@ -191,7 +191,7 @@
@Test
public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES);
+ TEST_USER_HANDLES, null);
for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
@@ -208,7 +208,7 @@
public void allCategoriesSpecified_enabledForAllUserHandles() {
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- userHandles);
+ userHandles, null);
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -225,7 +225,7 @@
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
- userHandles);
+ userHandles, null);
for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true),
@@ -239,7 +239,7 @@
mock(FabricatedOverlay.class)
};
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation,
- TEST_USER.getIdentifier(), TEST_USER_HANDLES);
+ TEST_USER.getIdentifier(), TEST_USER_HANDLES, null);
for (FabricatedOverlay overlay : pendingCreation) {
verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
@@ -253,7 +253,7 @@
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES);
+ TEST_USER_HANDLES, null);
for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -270,7 +270,7 @@
@Test
public void zeroCategoriesSpecified_allDisabled() {
mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES);
+ TEST_USER_HANDLES, null);
for (String category : THEME_CATEGORIES) {
verify(mTransactionBuilder).setEnabled(
@@ -285,7 +285,7 @@
categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
- TEST_USER_HANDLES);
+ TEST_USER_HANDLES, null);
verify(mTransactionBuilder, never()).setEnabled(
eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..c02583a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.UiModeManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
@@ -129,6 +130,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private UiModeManager mUiModeManager;
+ @Mock
+ private ActivityManager mActivityManager;
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
@Captor
@@ -164,7 +167,7 @@
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -224,7 +227,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -249,7 +252,7 @@
mBroadcastReceiver.getValue().onReceive(null, intent);
mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -263,7 +266,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
// Should not change theme after changing wallpapers, if intent doesn't have
// WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true.
@@ -272,7 +275,7 @@
mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -294,7 +297,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -333,7 +336,7 @@
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -367,8 +370,7 @@
assertThat(updatedSetting.getValue().contains(
"android.theme.customization.color_both\":\"0")).isTrue();
- verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -423,7 +425,7 @@
assertThat(updatedSetting.getValue().contains(
"android.theme.customization.color_both\":\"1")).isTrue();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -492,7 +494,7 @@
"android.theme.customization.color_both\":\"1")).isTrue();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -523,7 +525,7 @@
assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -554,7 +556,7 @@
assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
.isFalse();
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -587,7 +589,7 @@
anyInt());
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -620,7 +622,7 @@
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
// Apply overlay by existing theme from secure setting
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -653,7 +655,7 @@
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -675,7 +677,7 @@
ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
// Assert that we received secondary user colors
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -689,7 +691,7 @@
mBroadcastReceiver.getValue().onReceive(null,
new Intent(Intent.ACTION_PROFILE_ADDED)
.putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -700,7 +702,7 @@
new Intent(Intent.ACTION_PROFILE_ADDED)
.putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -711,7 +713,7 @@
new Intent(Intent.ACTION_PROFILE_ADDED)
.putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -723,7 +725,7 @@
(new Intent(Intent.ACTION_PROFILE_ADDED))
.putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE));
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@@ -737,7 +739,7 @@
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
// Regression test: null events should not reset the internal state and allow colors to be
// applied again.
@@ -747,11 +749,11 @@
mBroadcastReceiver.getValue().onReceive(null, intent);
mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
- any());
+ any(), any());
mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN),
null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
- any());
+ any(), any());
}
@Test
@@ -770,7 +772,7 @@
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -791,7 +793,7 @@
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
// Colors were applied during controller initialization.
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
clearInvocations(mThemeOverlayApplier);
}
@@ -810,7 +812,7 @@
mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
- mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+ mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
@VisibleForTesting
protected boolean isNightMode() {
return false;
@@ -831,7 +833,7 @@
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
// Colors were applied during controller initialization.
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
clearInvocations(mThemeOverlayApplier);
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -853,12 +855,12 @@
// Defers event because we already have initial colors.
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
// Then event happens after setup phase is over.
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
mDeviceProvisionedListener.getValue().onUserSetupChanged();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -881,11 +883,11 @@
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -907,10 +909,10 @@
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
}
@Test
@@ -930,7 +932,7 @@
ArgumentCaptor.forClass(Map.class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -949,19 +951,19 @@
mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
clearInvocations(mThemeOverlayApplier);
// Set to the same colors.
mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM,
USER_SYSTEM);
verify(mThemeOverlayApplier, never())
- .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
// Verify that no change resulted.
mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
- any());
+ any(), any());
}
@Test
@@ -975,7 +977,7 @@
ArgumentCaptor.forClass(FabricatedOverlay[].class);
verify(mThemeOverlayApplier)
- .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any());
+ .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any(), any());
FabricatedOverlay[] overlays = themeOverlays.getValue();
FabricatedOverlay accents = overlays[0];
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index b6a033a..1b43851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.processWrapper
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -1147,6 +1148,7 @@
uiEventLogger = uiEventLogger,
featureFlags = kosmos.fakeFeatureFlagsClassic,
userRestrictionChecker = mock(),
+ processWrapper = kosmos.processWrapper,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 21d4549..661837b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapperFake
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -264,6 +265,7 @@
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
userRestrictionChecker = mock(),
+ processWrapper = ProcessWrapperFake()
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index d0804be..5661e20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapperFake
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -176,6 +177,7 @@
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
userRestrictionChecker = mock(),
+ processWrapper = ProcessWrapperFake()
),
guestUserInteractor = guestUserInteractor,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 7a8dce8..8a33778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -64,7 +64,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
@@ -102,8 +101,6 @@
import java.util.Arrays;
import java.util.function.Predicate;
-import kotlinx.coroutines.Dispatchers;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -208,8 +205,6 @@
mDumpManager,
mLazySecureSettings,
mVibratorHelper,
- Dispatchers.getUnconfined(),
- TestScopeProvider.getTestScope(),
new FakeSystemClock());
mDialog.init(0, null);
State state = createShellState();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 8263174..fccb936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -37,10 +37,10 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -68,7 +68,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private ActivityLaunchAnimator.Controller mAnimationController;
+ private ActivityTransitionAnimator.Controller mAnimationController;
@Captor
private ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
@Captor
@@ -219,7 +219,7 @@
public void getQuickAccessUiIntent_hasCards_noPendingIntent_startsWalletActivity() {
mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true);
verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true),
- any(ActivityLaunchAnimator.Controller.class), eq(true));
+ any(ActivityTransitionAnimator.Controller.class), eq(true));
Intent intent = mIntentCaptor.getValue();
assertEquals(intent.getAction(), Intent.ACTION_VIEW);
assertEquals(
@@ -231,7 +231,7 @@
public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() {
mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false);
verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0),
- any(ActivityLaunchAnimator.Controller.class));
+ any(ActivityTransitionAnimator.Controller.class));
Intent intent = mIntentCaptor.getValue();
assertEquals(intent.getAction(), Intent.ACTION_VIEW);
assertEquals(
@@ -254,7 +254,7 @@
}).when(mQuickAccessWalletClient).getWalletPendingIntent(any(), any());
mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true);
verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntentCaptor.capture(),
- any(ActivityLaunchAnimator.Controller.class));
+ any(ActivityTransitionAnimator.Controller.class));
PendingIntent pendingIntent = mPendingIntentCaptor.getValue();
Intent intent = pendingIntent.getIntent();
assertEquals(intent.getAction(), Intent.ACTION_VIEW);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
similarity index 88%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 128f58b..66c9afb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,4 +18,4 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
index f723a9e5..5b84a41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
@@ -36,7 +36,7 @@
object : AnimationFeatureFlags {
override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
},
- launchAnimator = fakeLaunchAnimator(),
+ transitionAnimator = fakeTransitionAnimator(),
isForTesting = true,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index 0983041..bc7ec3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -16,19 +16,19 @@
import com.android.app.animation.Interpolators
-/** A [LaunchAnimator] to be used in tests. */
-fun fakeLaunchAnimator(): LaunchAnimator {
- return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+/** A [TransitionAnimator] to be used in tests. */
+fun fakeTransitionAnimator(): TransitionAnimator {
+ return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
}
/**
- * A [LaunchAnimator.Timings] to be used in tests.
+ * A [TransitionAnimator.Timings] to be used in tests.
*
* Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
* when computing the progress of a sub-animation (the contents fade in/out).
*/
private val TEST_TIMINGS =
- LaunchAnimator.Timings(
+ TransitionAnimator.Timings(
totalDuration = 0L,
contentBeforeFadeOutDelay = 1L,
contentBeforeFadeOutDuration = 1L,
@@ -36,9 +36,9 @@
contentAfterFadeInDuration = 1L
)
-/** A [LaunchAnimator.Interpolators] to be used in tests. */
+/** A [TransitionAnimator.Interpolators] to be used in tests. */
private val TEST_INTERPOLATORS =
- LaunchAnimator.Interpolators(
+ TransitionAnimator.Interpolators(
positionInterpolator = Interpolators.STANDARD,
positionXInterpolator = Interpolators.STANDARD,
contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..5485f79
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.app.admin.devicePolicyManager
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.settings.fakeSettings
+
+val Kosmos.communalSettingsRepository: CommunalSettingsRepository by
+ Kosmos.Fixture {
+ CommunalSettingsRepositoryImpl(
+ bgDispatcher = testDispatcher,
+ featureFlagsClassic = featureFlagsClassic,
+ secureSettings = fakeSettings,
+ broadcastDispatcher = broadcastDispatcher,
+ devicePolicyManager = devicePolicyManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index cccd908..ae7d877 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -16,7 +16,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
class FakeCommunalRepository(
applicationScope: CoroutineScope,
- override var isCommunalEnabled: Boolean = true,
override val desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT),
) : CommunalRepository {
@@ -40,21 +39,10 @@
_transitionState.value = transitionState
}
- fun setIsCommunalEnabled(value: Boolean) {
- isCommunalEnabled = value
- }
-
private val _isCommunalHubShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isCommunalHubShowing: Flow<Boolean> = _isCommunalHubShowing
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
-
- private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
-
- fun setCommunalEnabledState(enabled: Boolean) {
- _communalEnabledState.value = enabled
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c47f020..f7e9a11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -27,7 +27,6 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.smartspace.data.repository.smartspaceRepository
-import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
@@ -38,12 +37,12 @@
mediaRepository = communalMediaRepository,
communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
- userRepository = userRepository,
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
+ communalSettingsInteractor = communalSettingsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
new file mode 100644
index 0000000..b4773f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.communalSettingsInteractor by Fixture {
+ CommunalSettingsInteractor(
+ bgScope = applicationCoroutineScope,
+ repository = communalSettingsRepository,
+ userInteractor = selectedUserInteractor,
+ tableLogBuffer = mock(),
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index 9776b43..00fdced 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -31,6 +31,7 @@
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
communalInteractor = communalInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
index 21cff0d..3b3e23e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
@@ -18,5 +18,7 @@
import com.android.systemui.kosmos.Kosmos
+var Kosmos.fakeFaceWakeUpTriggersConfig by Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+
var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by
- Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+ Kosmos.Fixture { fakeFaceWakeUpTriggersConfig }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 5575b05..a8fc27a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -27,7 +27,6 @@
import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -51,7 +50,7 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
faceAuthenticationLogger = faceAuthLogger,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+ deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
userRepository = userRepository,
facePropertyRepository = facePropertyRepository,
faceWakeUpTriggersConfig = faceWakeUpTriggersConfig,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 85a233fd..534f773 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -20,6 +20,7 @@
import com.android.keyguard.KeyguardClockSwitch.ClockSize
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import org.mockito.Mockito
class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepository {
private val _clockSize = MutableStateFlow(LARGE)
@@ -42,6 +44,9 @@
private val _currentClock = MutableStateFlow(null)
override val currentClock = _currentClock
+
+ private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
+ override val previewClock: StateFlow<ClockController> = _previewClock
override val clockEventController: ClockEventController
get() = mock()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index a8f45b0..6f168d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -33,6 +33,7 @@
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+ aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
keyguardClockViewModel = keyguardClockViewModel,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d376f12..24bb9c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -38,6 +38,9 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
index 9841778..dee3644 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
@@ -16,11 +16,17 @@
package com.android.systemui.process
+import android.os.UserHandle
+
class ProcessWrapperFake : ProcessWrapper() {
var systemUser: Boolean = false
+ var userHandle: UserHandle = UserHandle.getUserHandleForUid(0)
+
override fun isSystemUser(): Boolean {
return systemUser
}
+
+ override fun myUserHandle() = userHandle
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index e67df9d..8e430db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -26,6 +26,8 @@
class FakeQSSceneAdapter(
private val inflateDelegate: suspend (Context) -> View,
+ override val qqsHeight: Int = 0,
+ override val qsHeight: Int = 0,
) : QSSceneAdapter {
private val _customizing = MutableStateFlow(false)
override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
new file mode 100644
index 0000000..5dc0333
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.shadeBackActionInteractor by
+ Kosmos.Fixture {
+ ShadeBackActionInteractorImpl(
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 998e579..37b2b76 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
@@ -30,14 +34,18 @@
val Kosmos.notificationListViewModel by Fixture {
NotificationListViewModel(
- shelf = notificationShelfViewModel,
- hideListViewModel = hideListViewModel,
- footer = Optional.of(footerViewModel),
- logger = Optional.of(notificationListLoggerViewModel),
- activeNotificationsInteractor = activeNotificationsInteractor,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
- seenNotificationsInteractor = seenNotificationsInteractor,
- shadeInteractor = shadeInteractor,
- zenModeInteractor = zenModeInteractor,
+ notificationShelfViewModel,
+ hideListViewModel,
+ Optional.of(footerViewModel),
+ Optional.of(notificationListLoggerViewModel),
+ activeNotificationsInteractor,
+ keyguardInteractor,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ remoteInputInteractor,
+ seenNotificationsInteractor,
+ shadeInteractor,
+ userSetupInteractor,
+ zenModeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 549a775..30d4105 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,13 +19,16 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +44,9 @@
shadeInteractor = shadeInteractor,
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 8042b5c..c83710a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -23,7 +23,7 @@
import com.android.internal.logging.metricsLogger
import com.android.internal.widget.lockPatternUtils
import com.android.systemui.activityIntentHelper
-import com.android.systemui.animation.activityLaunchAnimator
+import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.assist.assistManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
@@ -78,7 +78,7 @@
notificationPresenter,
shadeViewController,
notificationShadeWindowController,
- activityLaunchAnimator,
+ activityTransitionAnimator,
shadeAnimationInteractor,
notificationLaunchAnimatorControllerProvider,
launchFullScreenIntentProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 4e2dc7a..1504df4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -28,6 +28,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.process.processWrapper
import com.android.systemui.telephony.domain.interactor.telephonyInteractor
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.utils.userRestrictionChecker
@@ -53,5 +54,6 @@
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
userRestrictionChecker = userRestrictionChecker,
+ processWrapper = processWrapper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index 128f58b..bcb5848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.animation
+package com.android.systemui.util.settings
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f68baf5..592c6f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -30,11 +30,11 @@
private final List<FlashlightListener> callbacks = new ArrayList<>();
@VisibleForTesting
- public boolean isAvailable;
+ public boolean isAvailable = true;
@VisibleForTesting
- public boolean isEnabled;
+ public boolean isEnabled = false;
@VisibleForTesting
- public boolean hasFlashlight;
+ public boolean hasFlashlight = true;
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
@@ -52,16 +52,26 @@
callbacks.forEach(FlashlightListener::onFlashlightError);
}
+ /**
+ * Used to decide if tile should be shown or gone
+ * @return available/unavailable
+ */
@Override
public boolean hasFlashlight() {
return hasFlashlight;
}
+ /**
+ * @param newState active/inactive
+ */
@Override
public void setFlashlight(boolean newState) {
callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
+ /**
+ * @return temporary availability
+ */
@Override
public boolean isAvailable() {
return isAvailable;
@@ -76,6 +86,9 @@
public void addCallback(FlashlightListener listener) {
super.addCallback(listener);
callbacks.add(listener);
+
+ listener.onFlashlightAvailabilityChanged(isAvailable());
+ listener.onFlashlightChanged(isEnabled());
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 78f07e4..26c1bc9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -818,7 +818,7 @@
}
// Don't need to add the embedded hierarchy windows into the accessibility windows list.
- if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+ if (isEmbeddedHierarchyWindowsLocked(windowId)) {
return null;
}
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
@@ -866,21 +866,6 @@
return reportedWindow;
}
- private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
- final IBinder leashToken = mWindowIdMap.get(windowId);
- if (leashToken == null) {
- return false;
- }
-
- for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
- if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
- return true;
- }
- }
-
- return false;
- }
-
private int getTypeForWindowManagerWindowType(int windowType) {
switch (windowType) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
@@ -943,17 +928,11 @@
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
void dumpLocked(FileDescriptor fd, final PrintWriter pw, String[] args) {
- pw.append("Global Info [ ");
- pw.println("Top focused display Id = " + mTopFocusedDisplayId);
- pw.println(" Active Window Id = " + mActiveWindowId);
- pw.println(" Top Focused Window Id = " + mTopFocusedWindowId);
- pw.println(" Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId
- + " ]");
if (mIsProxy) {
pw.println("Proxy accessibility focused window = "
+ mProxyDisplayAccessibilityFocusedWindow);
+ pw.println();
}
- pw.println();
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int j = 0; j < windowCount; j++) {
@@ -1490,7 +1469,7 @@
* @return The windowId of the parent window, or self if no parent exists
*/
public int resolveParentWindowIdLocked(int windowId) {
- final IBinder token = getTokenLocked(windowId);
+ final IBinder token = getLeashTokenLocked(windowId);
if (token == null) {
return windowId;
}
@@ -2095,7 +2074,7 @@
* @param windowId The windowID.
* @return The token, or {@code NULL} if this windowID doesn't exist
*/
- IBinder getTokenLocked(int windowId) {
+ IBinder getLeashTokenLocked(int windowId) {
return mWindowIdMap.get(windowId);
}
@@ -2124,6 +2103,23 @@
}
/**
+ * Checks if the window is embedded into another window so that the window should be excluded
+ * from the exposed accessibility windows, and the node tree should be embedded in the host.
+ */
+ boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+ if (mHostEmbeddedMap.size() == 0) {
+ return false;
+ }
+
+ final IBinder leashToken = getLeashTokenLocked(windowId);
+ if (leashToken == null) {
+ return false;
+ }
+
+ return mHostEmbeddedMap.containsKey(leashToken);
+ }
+
+ /**
* Checks if the window belongs to a proxy display and if so clears the focused window id.
* @param focusClearedWindowId the cleared window id.
* @return true if an observer is proxy-ed and has cleared its focused window id.
@@ -2199,6 +2195,13 @@
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ pw.append("Global Info [ ");
+ pw.println("Top focused display Id = " + mTopFocusedDisplayId);
+ pw.println(" Active Window Id = " + mActiveWindowId);
+ pw.println(" Top Focused Window Id = " + mTopFocusedWindowId);
+ pw.println(" Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId
+ + " ]");
+ pw.println();
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fd8ab96..e1291e5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -451,7 +451,7 @@
final Session.SaveResult saveResult = session.showSaveLocked();
- session.logContextCommitted(saveResult.getNoSaveUiReason(), commitReason);
+ session.logContextCommittedLocked(saveResult.getNoSaveUiReason(), commitReason);
if (saveResult.isLogSaveShown()) {
session.logSaveUiShown();
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 83d9cdb..b89e0d8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,6 +16,8 @@
package com.android.server.autofill;
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
import static android.service.autofill.AutofillService.WEBVIEW_REQUESTED_CREDENTIAL_KEY;
@@ -113,6 +115,8 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ServiceInfo;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialResponse;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -123,10 +127,12 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.service.assist.classification.FieldClassificationRequest;
import android.service.assist.classification.FieldClassificationResponse;
@@ -153,6 +159,7 @@
import android.service.autofill.SaveRequest;
import android.service.autofill.UserData;
import android.service.autofill.ValueFinder;
+import android.service.credentials.CredentialProviderService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -2507,7 +2514,7 @@
+ id + " destroyed");
return;
}
- fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null);
+ fillInIntent = createAuthFillInIntentLocked(requestId, extras);
if (fillInIntent == null) {
forceRemoveFromServiceLocked();
return;
@@ -2808,6 +2815,7 @@
mSessionFlags.mExpiredResponse = false;
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
@@ -2818,6 +2826,12 @@
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_SUCCESS);
replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
+ } else if (result instanceof GetCredentialResponse) {
+ Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
+ Dataset dataset = getDatasetFromCredentialResponse((GetCredentialResponse) result);
+ if (dataset != null) {
+ autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN);
+ }
} else if (result instanceof Dataset) {
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
logAuthenticationStatusLocked(requestId,
@@ -2854,6 +2868,17 @@
}
}
+ private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
+ if (result == null) {
+ return null;
+ }
+ Bundle bundle = result.getCredential().getData();
+ if (bundle == null) {
+ return null;
+ }
+ return bundle.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, Dataset.class);
+ }
+
Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
response = getEffectiveFillResponse(response);
@@ -3061,6 +3086,10 @@
* when necessary.
*/
public void logContextCommitted() {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN
+ + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE);
+ }
mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
Event.NO_SAVE_UI_REASON_NONE,
COMMIT_REASON_UNKNOWN));
@@ -3069,16 +3098,26 @@
/**
* Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
- * when necessary.
+ * when necessary. Note that it could be called before save UI is shown and the session is
+ * committed.
*
* @param saveDialogNotShowReason The reason why a save dialog was not shown.
* @param commitReason The reason why context is committed.
*/
- public void logContextCommitted(@NoSaveReason int saveDialogNotShowReason,
+
+ @GuardedBy("mLock")
+ public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason,
@AutofillCommitReason int commitReason) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
+ + " no_save_reason:" + saveDialogNotShowReason);
+ }
mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
saveDialogNotShowReason, commitReason));
- logAllEvents(commitReason);
+
+ mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
+ mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
}
private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason,
@@ -3134,6 +3173,10 @@
@Nullable ArrayList<FieldClassification> detectedFieldClassifications,
@NoSaveReason int saveDialogNotShowReason,
@AutofillCommitReason int commitReason) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
+ + " no_save_reason:" + saveDialogNotShowReason);
+ }
final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)");
if (lastResponse == null) return;
@@ -3310,7 +3353,9 @@
changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
mComponentName, mCompatMode, saveDialogNotShowReason);
- logAllEvents(commitReason);
+ mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
+ mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason);
}
/**
@@ -3751,11 +3796,6 @@
}
}
- if (sDebug) {
- Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
- + id + "!");
- }
-
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(new Binder(), id, client);
@@ -3787,6 +3827,10 @@
}
}
mSessionFlags.mShowingSaveUi = true;
+ if (sDebug) {
+ Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
+ + id + "!");
+ }
return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_NONE);
}
@@ -4690,6 +4734,11 @@
}
+ if (isCredmanIntegrationActive(response)) {
+ Slog.d(TAG, "Attempting to add Credential Manager callback to pinned entries");
+ addCredentialManagerCallback(response);
+ }
+
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -4749,6 +4798,11 @@
}
}
+ private boolean isCredmanIntegrationActive(FillResponse response) {
+ return Flags.autofillCredmanIntegration()
+ && (response.getFlags() & FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0;
+ }
+
@GuardedBy("mLock")
private void updateFillDialogTriggerIdsLocked() {
final FillResponse response = getLastResponseLocked(null);
@@ -4964,6 +5018,69 @@
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
+ private void addCredentialManagerCallback(FillResponse response) {
+ if (response.getDatasets() == null) {
+ return;
+ }
+ for (Dataset dataset: response.getDatasets()) {
+ if (isPinnedDataset(dataset)) {
+ Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
+ addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
+ }
+ }
+ }
+
+ private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
+ final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+ Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+ GetCredentialResponse getCredentialResponse =
+ resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ GetCredentialResponse.class);
+ Dataset datasetFromCredential = getDatasetFromCredentialResponse(
+ getCredentialResponse);
+ if (datasetFromCredential != null) {
+ autoFill(requestId, /*datasetIndex=*/-1,
+ datasetFromCredential, false,
+ UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ }
+ } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+ GetCredentialException exception = resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
+ Slog.d(TAG, "Credman bottom sheet from pinned "
+ + "entry failed with: + " + exception.getType() + " , "
+ + exception.getMessage());
+ } else {
+ Slog.d(TAG, "Unknown resultCode from credential "
+ + "manager bottom sheet: " + resultCode);
+ }
+ }
+ };
+ ResultReceiver ipcFriendlyResultReceiver =
+ toIpcFriendlyResultReceiver(resultReceiver);
+
+ Intent metadataIntent = dataset.getCredentialFillInIntent();
+ metadataIntent.putExtra(
+ android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ ipcFriendlyResultReceiver);
+ dataset.setCredentialFillInIntent(metadataIntent);
+ }
+
+ private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
+ }
+
boolean isDestroyed() {
synchronized (mLock) {
return mDestroyed;
@@ -5669,8 +5786,14 @@
// does not matter the value of isPrimary because null response won't be overridden.
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH,
/* clearResponse= */ false, /* isPrimary= */ true);
- final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState,
- dataset.getAuthenticationExtras());
+ final Intent fillInIntent;
+ if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) {
+ Slog.d(TAG, "Setting credential fill intent");
+ fillInIntent = dataset.getCredentialFillInIntent();
+ } else {
+ fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
+ }
+
if (fillInIntent == null) {
forceRemoveFromServiceLocked();
return;
@@ -5686,8 +5809,7 @@
// TODO: this should never be null, but we got at least one occurrence, probably due to a race.
@GuardedBy("mLock")
@Nullable
- private Intent createAuthFillInIntentLocked(int requestId, Bundle extras,
- @Nullable Bundle authExtras) {
+ private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
final Intent fillInIntent = new Intent();
final FillContext context = getFillContextByRequestIdLocked(requestId);
@@ -5704,9 +5826,6 @@
}
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
- if (authExtras != null) {
- fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras);
- }
return fillInIntent;
}
@@ -6286,6 +6405,9 @@
@GuardedBy("mLock")
private void logAllEvents(@AutofillCommitReason int val) {
+ if (sVerbose) {
+ Slog.v(TAG, "logAllEvents(" + id + "): commitReason: " + val);
+ }
mSessionCommittedEventLogger.maybeSetCommitReason(val);
mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
mSessionCommittedEventLogger.maybeSetSessionDurationMillis(
@@ -6311,6 +6433,9 @@
@GuardedBy("mLock")
RemoteFillService destroyLocked() {
// Log unlogged events.
+ if (sVerbose) {
+ Slog.v(TAG, "destroyLocked for session: " + id);
+ }
logAllEvents(COMMIT_REASON_SESSION_DESTROYED);
if (mDestroyed) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
new file mode 100644
index 0000000..69c7b29
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.ButtonBarLayout;
+
+/** An extension of {@link ButtonBarLayout} for use in Autofill bottom sheets. */
+public class BottomSheetButtonBarLayout extends ButtonBarLayout {
+
+ public BottomSheetButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final View spacer = findViewById(R.id.autofill_button_bar_spacer);
+ if (spacer == null) {
+ return;
+ }
+ if (isStacked()) {
+ spacer.getLayoutParams().width = 0;
+ spacer.getLayoutParams().height =
+ getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_height);
+ setGravity(Gravity.CENTER_VERTICAL | Gravity.END);
+ } else {
+ spacer.getLayoutParams().width =
+ getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_width);
+ spacer.getLayoutParams().height = 0;
+ setGravity(Gravity.CENTER_VERTICAL);
+ }
+ }
+
+ private boolean isStacked() {
+ return getOrientation() == LinearLayout.VERTICAL;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index b2716ec..d580f3a 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,6 +15,7 @@
*/
package com.android.server.autofill.ui;
+import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static com.android.server.autofill.Helper.paramsToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -31,6 +32,7 @@
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetFieldFilter;
import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
import android.text.TextUtils;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
@@ -79,6 +81,7 @@
com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill;
+ private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
private static final TypedValue sTempTypedValue = new TypedValue();
@@ -211,7 +214,11 @@
if (sVerbose) {
Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
}
- } else {
+ } else if (Flags.autofillCredmanIntegration() && (
+ (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+ mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
+ }
+ else {
mVisibleDatasetsMaxCount = mContext.getResources()
.getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0054bc8..b43f1a9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1305,6 +1305,8 @@
mAssociationStore.dump(out);
mDevicePresenceMonitor.dump(out);
mCompanionAppController.dump(out);
+ mTransportManager.dump(out);
+ mSystemDataTransferRequestStore.dump(out);
}
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 51c5fd6..c4c80f9 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -48,6 +48,7 @@
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -303,6 +304,32 @@
}
}
+
+
+ /**
+ * Dumps current system data transfer request states.
+ */
+ public void dump(@NonNull PrintWriter out) {
+ synchronized (mLock) {
+ out.append("System Data Transfer Requests (Cached): ");
+ if (mCachedPerUser.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int i = 0; i < mCachedPerUser.size(); i++) {
+ final int userId = mCachedPerUser.keyAt(i);
+ for (SystemDataTransferRequest request : mCachedPerUser.get(userId)) {
+ out.append(" u")
+ .append(String.valueOf(userId))
+ .append(" -> ")
+ .append(request.toString())
+ .append('\n');
+ }
+ }
+ }
+ }
+ }
+
private void writeRequestsToXml(@NonNull TypedXmlSerializer serializer,
@Nullable Collection<SystemDataTransferRequest> requests) throws IOException {
serializer.startTag(null, XML_TAG_REQUESTS);
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3e45626..3861f99 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -36,6 +36,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -225,6 +226,25 @@
}
/**
+ * Dumps current list of active transports.
+ */
+ public void dump(@NonNull PrintWriter out) {
+ synchronized (mTransports) {
+ out.append("System Data Transports: ");
+ if (mTransports.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int i = 0; i < mTransports.size(); i++) {
+ final int associationId = mTransports.keyAt(i);
+ final Transport transport = mTransports.get(associationId);
+ out.append(" ").append(transport.toString()).append('\n');
+ }
+ }
+ }
+ }
+
+ /**
* @hide
*/
public void enableSecureTransport(boolean enabled) {
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index ca169aac..05703ce 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -94,6 +94,13 @@
}
}
+ @Override
+ public String toString() {
+ return "RawTransport{"
+ + "mAssociationId=" + mAssociationId
+ + '}';
+ }
+
private void receiveMessage() throws IOException {
synchronized (mRemoteIn) {
final byte[] headerBytes = new byte[HEADER_LENGTH];
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 6e906eb..1e95e65 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -152,4 +152,12 @@
close();
}
}
+
+ @Override
+ public String toString() {
+ return "SecureTransport{"
+ + "mAssociationId=" + mAssociationId
+ + ", mSecureChannel=" + mSecureChannel
+ + '}';
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8962bf0..1b49f18e 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -692,32 +692,37 @@
private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
- WaitForDevice(String deviceName, int vendorId, int productId) {
+ WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
mListener = new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
- final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
- deviceId);
- Objects.requireNonNull(device, "Newly added input device was null.");
- if (!device.getName().equals(deviceName)) {
- return;
- }
- final InputDeviceIdentifier id = device.getIdentifier();
- if (id.getVendorId() != vendorId || id.getProductId() != productId) {
- return;
- }
- mInputDeviceId = deviceId;
- mDeviceAddedLatch.countDown();
+ onInputDeviceChanged(deviceId);
}
@Override
public void onInputDeviceRemoved(int deviceId) {
-
}
@Override
public void onInputDeviceChanged(int deviceId) {
+ if (isMatchingDevice(deviceId)) {
+ mInputDeviceId = deviceId;
+ mDeviceAddedLatch.countDown();
+ }
+ }
+ private boolean isMatchingDevice(int deviceId) {
+ final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
+ deviceId);
+ Objects.requireNonNull(device, "Newly added input device was null.");
+ if (!device.getName().equals(deviceName)) {
+ return false;
+ }
+ final InputDeviceIdentifier id = device.getIdentifier();
+ if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+ return false;
+ }
+ return device.getAssociatedDisplayId() == associatedDisplayId;
}
};
InputManagerGlobal.getInstance().registerInputDeviceListener(mListener, mHandler);
@@ -799,7 +804,7 @@
final int inputDeviceId;
setUniqueIdAssociation(displayId, phys);
- try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+ try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
ptr = deviceOpener.get();
// See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
if (ptr == 0) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9d9e7c9..7979936 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,8 +16,8 @@
package com.android.server;
-import static android.os.Flags.stateOfHealthPublic;
import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
+import static android.os.Flags.stateOfHealthPublic;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
@@ -81,6 +81,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -157,6 +158,12 @@
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
private int mLastCharingState;
+ /**
+ * The last seen charging policy. This requires the
+ * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
+ * included in the ACTION_BATTERY_CHANGED intent extras.
+ */
+ private int mLastChargingPolicy;
private int mSequence = 1;
@@ -197,6 +204,9 @@
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
+ private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
+ mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
+
private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -527,6 +537,11 @@
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
+ if (force || mHealthInfo.chargingPolicy != mLastChargingPolicy) {
+ mLastChargingPolicy = mHealthInfo.chargingPolicy;
+ mHandler.post(this::notifyChargingPolicyChanged);
+ }
+
if (force
|| (mHealthInfo.batteryStatus != mLastBatteryStatus
|| mHealthInfo.batteryHealth != mLastBatteryHealth
@@ -827,6 +842,17 @@
mLastBatteryLevelChangedSentMs = SystemClock.elapsedRealtime();
}
+ private void notifyChargingPolicyChanged() {
+ final int newPolicy;
+ synchronized (mLock) {
+ newPolicy = mLastChargingPolicy;
+ }
+ for (BatteryManagerInternal.ChargingPolicyChangeListener listener
+ : mChargingPolicyChangeListeners) {
+ listener.onChargingPolicyChanged(newPolicy);
+ }
+ }
+
// TODO: Current code doesn't work since "--unplugged" flag in BSS was purposefully removed.
private void logBatteryStatsLocked() {
IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
@@ -1220,6 +1246,8 @@
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
+ pw.println(" Charging state: " + mHealthInfo.chargingState);
+ pw.println(" Charging policy: " + mHealthInfo.chargingPolicy);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -1452,6 +1480,19 @@
}
@Override
+ public void registerChargingPolicyChangeListener(
+ BatteryManagerInternal.ChargingPolicyChangeListener listener) {
+ mChargingPolicyChangeListeners.add(listener);
+ }
+
+ @Override
+ public int getChargingPolicy() {
+ synchronized (mLock) {
+ return mLastChargingPolicy;
+ }
+ }
+
+ @Override
public int getInvalidCharger() {
synchronized (mLock) {
return mInvalidCharger;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 9554e63..fb527c1 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -20,8 +20,9 @@
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.Ringtone;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
@@ -305,16 +306,11 @@
if (soundPath != null) {
final Uri soundUri = Uri.parse("file://" + soundPath);
if (soundUri != null) {
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build();
- final Ringtone sfx = new Ringtone.Builder(getContext(),
- Ringtone.MEDIA_SOUND, audioAttributes)
- .setUri(soundUri)
- .setPreferBuiltinDevice()
- .build();
+ final Ringtone sfx = RingtoneManager.getRingtone(
+ getContext(), soundUri);
if (sfx != null) {
+ sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.preferBuiltinDevice(true);
sfx.play();
}
}
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 627a62e..34c3d7e 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -246,16 +246,6 @@
@Override
public void run() {
- if (mGuid.isEmpty()) {
- Slog.e(TAG, "adbwifi guid was not set");
- return;
- }
- mPort = native_pairing_start(mGuid, mPairingCode);
- if (mPort <= 0 || mPort > 65535) {
- Slog.e(TAG, "Unable to start pairing server");
- return;
- }
-
// Register the mdns service
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName(mServiceName);
@@ -288,6 +278,28 @@
mHandler.sendMessage(message);
}
+ @Override
+ public void start() {
+ /*
+ * If a user is fast enough to click cancel, native_pairing_cancel can be invoked
+ * while native_pairing_start is running which run the destruction of the object
+ * while it is being constructed. Here we start the pairing server on foreground
+ * Thread so native_pairing_cancel can never be called concurrently. Then we let
+ * the pairing server run on a background Thread.
+ */
+ if (mGuid.isEmpty()) {
+ Slog.e(TAG, "adbwifi guid was not set");
+ return;
+ }
+ mPort = native_pairing_start(mGuid, mPairingCode);
+ if (mPort <= 0) {
+ Slog.e(TAG, "Unable to start pairing server");
+ return;
+ }
+
+ super.start();
+ }
+
public void cancelPairing() {
native_pairing_cancel();
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index adc0255..cd45b03 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -581,7 +581,8 @@
if (DEBUG_FOREGROUND_SERVICE) {
Slog.i(TAG, " Stopping fg for service " + r);
}
- setServiceForegroundInnerLocked(r, 0, null, 0, 0);
+ setServiceForegroundInnerLocked(r, 0, null, 0, 0,
+ 0);
}
}
@@ -989,7 +990,7 @@
if (fgRequired) {
logFgsBackgroundStart(r);
- if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
+ if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r, callingUid)) {
String msg = "startForegroundService() not allowed due to "
+ "mAllowStartForeground false: service "
+ r.shortInstanceName;
@@ -1787,11 +1788,13 @@
public void setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags, int foregroundServiceType) {
final int userId = UserHandle.getCallingUserId();
+ final int callingUid = mAm.mInjector.getCallingUid();
final long origId = mAm.mInjector.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
- setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType);
+ setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType,
+ callingUid);
}
} finally {
mAm.mInjector.restoreCallingIdentity(origId);
@@ -2106,7 +2109,8 @@
*/
@GuardedBy("mAm")
private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
- Notification notification, int flags, int foregroundServiceType) {
+ Notification notification, int flags, int foregroundServiceType,
+ int callingUidIfStart) {
if (id != 0) {
if (notification == null) {
throw new IllegalArgumentException("null notification");
@@ -2234,7 +2238,8 @@
}
// Whether FGS-BG-start restriction is enabled for this service.
- final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r);
+ final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r,
+ callingUidIfStart);
// Whether to extend the SHORT_SERVICE time out.
boolean extendShortServiceTimeout = false;
@@ -8486,14 +8491,43 @@
NOTE_FOREGROUND_SERVICE_BG_LAUNCH, n.build(), UserHandle.ALL);
}
- private boolean isBgFgsRestrictionEnabled(ServiceRecord r) {
- return mAm.mConstants.mFlagFgsStartRestrictionEnabled
- // Checking service's targetSdkVersion.
- && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)
- && (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk
- // Checking callingUid's targetSdkVersion.
- || CompatChanges.isChangeEnabled(
- FGS_BG_START_RESTRICTION_CHANGE_ID, r.mRecentCallingUid));
+ private boolean isBgFgsRestrictionEnabled(ServiceRecord r, int actualCallingUid) {
+ // mFlagFgsStartRestrictionEnabled controls whether to enable the BG FGS restrictions:
+ // - If true (default), BG-FGS restrictions are enabled if the service targets >= S.
+ // - If false, BG-FGS restrictions are disabled for all apps.
+ if (!mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+ return false;
+ }
+
+ // If the service target below S, then don't enable the restrictions.
+ if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)) {
+ return false;
+ }
+
+ // mFgsStartRestrictionCheckCallerTargetSdk controls whether we take the caller's target
+ // SDK level into account or not:
+ // - If true (default), BG-FGS restrictions only happens if the caller _also_ targets >= S.
+ // - If false, BG-FGS restrictions do _not_ use the caller SDK levels.
+ if (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk) {
+ return true; // In this case, we only check the service's target SDK level.
+ }
+ final int callingUid;
+ if (Flags.newFgsRestrictionLogic()) {
+ // We always consider SYSTEM_UID to target S+, so just enable the restrictions.
+ if (actualCallingUid == Process.SYSTEM_UID) {
+ return true;
+ }
+ callingUid = actualCallingUid;
+ } else {
+ // Legacy logic used mRecentCallingUid.
+ callingUid = r.mRecentCallingUid;
+ }
+ if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, callingUid)) {
+ return false; // If the caller targets < S, then we still disable the restrictions.
+ }
+
+ // Both the service and the caller target S+, so enable the check.
+ return true;
}
private void logFgsBackgroundStart(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a5531ae..2750344 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -187,6 +187,7 @@
import static com.android.server.wm.ActivityTaskManagerService.DUMP_VISIBLE_ACTIVITIES;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
import android.Manifest;
import android.Manifest.permission;
@@ -521,6 +522,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -921,6 +923,15 @@
@GuardedBy("this")
final ComponentAliasResolver mComponentAliasResolver;
+ private static final long HOME_LAUNCH_TIMEOUT_MS = 15000;
+ private final AtomicBoolean mHasHomeDelay = new AtomicBoolean(false);
+
+ /**
+ * Tracks all users with computed color resources by ThemeOverlaycvontroller
+ */
+ @GuardedBy("this")
+ private final Set<Integer> mThemeOverlayReadiness = new HashSet<>();
+
/**
* Tracks association information for a particular package along with debuggability.
* <p> Associations for a package A are allowed to package B if B is part of the
@@ -2332,6 +2343,7 @@
mService.startBroadcastObservers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mService.mPackageWatchdog.onPackagesReady();
+ mService.setHomeTimeout();
}
}
@@ -5304,6 +5316,59 @@
}
}
+ /**
+ * Starts Home if there is no completion signal from ThemeOverlayController
+ */
+ private void setHomeTimeout() {
+ if (enableHomeDelay() && mHasHomeDelay.compareAndSet(false, true)) {
+ mHandler.postDelayed(() -> {
+ if (!getThemeOverlayReadiness()) {
+ Slog.d(TAG,
+ "ThemeHomeDelay: ThemeOverlayController not responding, launching "
+ + "Home after "
+ + HOME_LAUNCH_TIMEOUT_MS + "ms");
+ setThemeOverlayReady(true);
+ }
+ }, HOME_LAUNCH_TIMEOUT_MS);
+ }
+ }
+
+ /**
+ * Used by ThemeOverlayController to notify all listeners for
+ * color palette readiness.
+ * @hide
+ */
+ @Override
+ public void setThemeOverlayReady(boolean readiness) {
+ enforceCallingPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY,
+ "setThemeOverlayReady");
+
+ int currentUserId = mUserController.getCurrentUserId();
+
+ boolean updateReadiness;
+ synchronized (mThemeOverlayReadiness) {
+ updateReadiness = readiness ? mThemeOverlayReadiness.add(currentUserId)
+ : mThemeOverlayReadiness.remove(currentUserId);
+ }
+
+ if (updateReadiness && readiness && enableHomeDelay()) {
+ mAtmInternal.startHomeOnAllDisplays(currentUserId, "setThemeOverlayReady");
+ }
+ }
+
+ /**
+ * Returns current state of ThemeOverlayController color
+ * palette readiness.
+ *
+ * @hide
+ */
+ public boolean getThemeOverlayReadiness() {
+ int uid = mUserController.getCurrentUserId();
+ synchronized (mThemeOverlayReadiness) {
+ return mThemeOverlayReadiness.contains(uid);
+ }
+ }
+
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
@@ -18033,6 +18098,10 @@
mAtmInternal.onUserStopped(userId);
// Clean up various services by removing the user
mBatteryStatsService.onUserRemoved(userId);
+
+ synchronized (mThemeOverlayReadiness) {
+ mThemeOverlayReadiness.remove(userId);
+ }
}
@Override
@@ -19391,6 +19460,11 @@
return ActivityManagerService.this.clearApplicationUserData(packageName, keepState,
isRestore, observer, userId);
}
+
+ @Override
+ public boolean getThemeOverlayReadiness() {
+ return ActivityManagerService.this.getThemeOverlayReadiness();
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c37e619..d1c8c30 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -152,6 +152,11 @@
private int mActiveIndex;
/**
+ * True if the broadcast actively being dispatched to this process was re-enqueued previously.
+ */
+ private boolean mActiveReEnqueued;
+
+ /**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
@@ -312,6 +317,7 @@
final SomeArgs broadcastArgs = SomeArgs.obtain();
broadcastArgs.arg1 = record;
broadcastArgs.argi1 = recordIndex;
+ broadcastArgs.argi2 = 1;
getQueueForBroadcast(record).addFirst(broadcastArgs);
onBroadcastEnqueued(record, recordIndex);
}
@@ -609,6 +615,7 @@
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
+ mActiveReEnqueued = (next.argi2 == 1);
mActiveCountSinceIdle++;
mActiveAssumedDeliveryCountSinceIdle +=
(mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
@@ -624,12 +631,21 @@
public void makeActiveIdle() {
mActive = null;
mActiveIndex = 0;
+ mActiveReEnqueued = false;
mActiveCountSinceIdle = 0;
mActiveAssumedDeliveryCountSinceIdle = 0;
mActiveViaColdStart = false;
invalidateRunnableAt();
}
+ public boolean wasActiveBroadcastReEnqueued() {
+ // If the flag is not enabled, treat as if the broadcast was never re-enqueued.
+ if (!Flags.avoidRepeatedBcastReEnqueues()) {
+ return false;
+ }
+ return mActiveReEnqueued;
+ }
+
/**
* Update summary statistics when the given record has been enqueued.
*/
@@ -1476,6 +1492,9 @@
if (runningOomAdjusted) {
pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
}
+ if (mActiveReEnqueued) {
+ pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued);
+ }
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index db0f03f..98263df 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -542,8 +542,8 @@
updateOomAdj |= queue.runningOomAdjusted;
try {
completed = scheduleReceiverWarmLocked(queue);
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
completed = true;
}
} else {
@@ -586,7 +586,12 @@
private void clearInvalidPendingColdStart() {
logw("Clearing invalid pending cold start: " + mRunningColdStart);
- mRunningColdStart.reEnqueueActiveBroadcast();
+ if (mRunningColdStart.wasActiveBroadcastReEnqueued()) {
+ finishReceiverActiveLocked(mRunningColdStart, BroadcastRecord.DELIVERY_FAILURE,
+ "invalid start with re-enqueued broadcast");
+ } else {
+ mRunningColdStart.reEnqueueActiveBroadcast();
+ }
demoteFromRunningLocked(mRunningColdStart);
clearRunningColdStart();
enqueueUpdateRunningList();
@@ -613,19 +618,26 @@
}
}
- private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
+ private void finishOrReEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
- final BroadcastRecord record = queue.getActive();
- final int index = queue.getActiveIndex();
- setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
- BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
- queue.reEnqueueActiveBroadcast();
+ if (queue.wasActiveBroadcastReEnqueued()) {
+ // If the broadcast was already re-enqueued previously, finish it to avoid repeated
+ // delivery attempts
+ finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "re-enqueued broadcast delivery failed");
+ } else {
+ final BroadcastRecord record = queue.getActive();
+ final int index = queue.getActiveIndex();
+ setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
+ BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
+ queue.reEnqueueActiveBroadcast();
+ }
}
@Override
public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app)
- throws BroadcastDeliveryFailedException {
+ throws BroadcastRetryException {
if (DEBUG_BROADCAST) {
logv("Process " + app + " is attached");
}
@@ -653,8 +665,8 @@
if (scheduleReceiverWarmLocked(queue)) {
demoteFromRunningLocked(queue);
}
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
demoteFromRunningLocked(queue);
throw e;
}
@@ -983,7 +995,7 @@
@CheckResult
@GuardedBy("mService")
private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue)
- throws BroadcastDeliveryFailedException {
+ throws BroadcastRetryException {
checkState(queue.isActive(), "isActive");
final int cookie = traceBegin("scheduleReceiverWarmLocked");
@@ -1065,7 +1077,7 @@
*/
@CheckResult
private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
- @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException {
+ @NonNull BroadcastRecord r, int index) throws BroadcastRetryException {
final ProcessRecord app = queue.app;
final Object receiver = r.receivers.get(index);
@@ -1157,7 +1169,7 @@
// to try redelivering the broadcast to this receiver.
if (receiver instanceof ResolveInfo) {
cancelDeliveryTimeoutLocked(queue);
- throw new BroadcastDeliveryFailedException(e);
+ throw new BroadcastRetryException(e);
}
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
"remote app");
@@ -1316,8 +1328,8 @@
demoteFromRunningLocked(queue);
return true;
}
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
demoteFromRunningLocked(queue);
return true;
}
diff --git a/services/core/java/com/android/server/am/BroadcastRetryException.java b/services/core/java/com/android/server/am/BroadcastRetryException.java
new file mode 100644
index 0000000..8bd6664
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastRetryException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.am;
+
+/**
+ * Exception to represent that broadcast delivery failed and we should try redelivering it.
+ */
+public class BroadcastRetryException extends BroadcastDeliveryFailedException {
+ public BroadcastRetryException(String name) {
+ super(name);
+ }
+
+ public BroadcastRetryException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 862542e..7d82f0c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1499,8 +1499,12 @@
&& !uidRec.isCurAllowListed()) {
// UID is now in the background (and not on the temp allowlist). Was it
// previously in the foreground (or on the temp allowlist)?
+ // Or, it wasn't in the foreground / allowlist, but its last background
+ // timestamp is also 0, this means it's never been in the
+ // foreground / allowlist since it's born at all.
if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
- || uidRec.isSetAllowListed()) {
+ || uidRec.isSetAllowListed()
+ || uidRec.getLastBackgroundTime() == 0) {
uidRec.setLastBackgroundTime(nowElapsed);
if (mService.mDeterministicUidIdle
|| !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
@@ -1526,6 +1530,7 @@
uidRec.setIdle(false);
}
uidRec.setLastBackgroundTime(0);
+ uidRec.setLastIdleTime(0);
}
final boolean wasCached = uidRec.getSetProcState()
> ActivityManager.PROCESS_STATE_RECEIVER;
@@ -3700,12 +3705,14 @@
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
final long bgTime = uidRec.getLastBackgroundTime();
- if (bgTime > 0 && !uidRec.isIdle()) {
+ final long idleTime = uidRec.getLastIdleTime();
+ if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
if (bgTime <= maxBgTime) {
EventLogTags.writeAmUidIdle(uidRec.getUid());
synchronized (mProcLock) {
uidRec.setIdle(true);
uidRec.setSetIdle(true);
+ uidRec.setLastIdleTime(nowElapsed);
}
mService.doStopUidLocked(uidRec.getUid(), uidRec);
} else {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 4329afc..45fd470 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -66,6 +66,9 @@
private long mLastBackgroundTime;
@CompositeRWLock({"mService", "mProcLock"})
+ private long mLastIdleTime;
+
+ @CompositeRWLock({"mService", "mProcLock"})
private boolean mEphemeral;
@CompositeRWLock({"mService", "mProcLock"})
@@ -255,6 +258,16 @@
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getLastIdleTime() {
+ return mLastIdleTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setLastIdleTime(long lastActiveTime) {
+ mLastIdleTime = lastActiveTime;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
boolean isEphemeral() {
return mEphemeral;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2b81dbc..96c6be8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -993,17 +993,20 @@
/**
* Stops a single User. This can also trigger locking user data out depending on device's
* config ({@code mDelayUserDataLocking}) and arguments.
- * User will be unlocked when
- * - {@code mDelayUserDataLocking} is not set.
- * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+ *
+ * In the default configuration for most device and users, users will be locked when stopping.
+ * User will remain unlocked only if all the following are true
+ * <li> {@link #canDelayDataLockingForUser(int)} (based on mDelayUserDataLocking) is true
+ * <li> the parameter {@code allowDelayedLocking} is true
+ * <li> {@code keyEvictedCallback} is null
* -
*
* @param userId User Id to stop and lock the data.
* @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
* later when number of unlocked users reaches
* {@code mMaxRunnngUsers}. Note that this is respected only when
- * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
- * null. Otherwise the user will be locked.
+ * delayed locking is enabled for this user and {@keyEvictedCallback}
+ * is null. Otherwise the user nonetheless will be locked.
* @param stopUserCallback Callback to notify that user has stopped.
* @param keyEvictedCallback Callback to notify that user has been unlocked.
*/
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 31d9cc9..16dbe18 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -42,3 +42,14 @@
description: "Optimize the service bindings by different policies like skipping oom adjuster"
bug: "318717054"
}
+
+flag {
+ namespace: "backstage_power"
+ name: "avoid_repeated_bcast_re_enqueues"
+ description: "Avoid re-enqueueing a broadcast repeatedly"
+ bug: "319225224"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bbbba26..04deb02 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12458,6 +12458,20 @@
return app;
}
+ /**
+ * Retrieves all audioMixes registered with the AudioPolicyManager
+ * @return list of registered audio mixes
+ */
+ public List<AudioMix> getRegisteredPolicyMixes() {
+ if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
+ return Collections.emptyList();
+ }
+
+ synchronized (mAudioPolicies) {
+ return mAudioSystem.getRegisteredPolicyMixes();
+ }
+ }
+
public int addMixForPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb) {
if (DEBUG_AP) { Log.d(TAG, "addMixForPolicy for " + pcb.asBinder()
+ " with config:" + policyConfig); }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 4f46dd1..49ab19a 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -27,6 +27,7 @@
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.Flags;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -42,6 +43,7 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -602,6 +604,23 @@
}
/**
+ * @return a list of AudioMixes that are registered in the audio policy manager.
+ */
+ public List<AudioMix> getRegisteredPolicyMixes() {
+ if (!Flags.audioMixTestApi()) {
+ return Collections.emptyList();
+ }
+
+ List<AudioMix> audioMixes = new ArrayList<>();
+ int result = AudioSystem.getRegisteredPolicyMixes(audioMixes);
+ if (result != AudioSystem.SUCCESS) {
+ throw new IllegalStateException(
+ "Cannot fetch registered policy mixes. Result: " + result);
+ }
+ return Collections.unmodifiableList(audioMixes);
+ }
+
+ /**
* Update already {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
*
* @param mixes - array of registered {@link AudioMix}-es to update.
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index fbd32a6..d061e2d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -31,6 +31,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import java.util.function.Supplier;
@@ -202,6 +203,16 @@
}
}
+ // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+ protected final void resetIgnoreDisplayTouches() {
+ final AidlSession session = (AidlSession) getFreshDaemon();
+ try {
+ session.getSession().setIgnoreDisplayTouches(false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when resetting setIgnoreDisplayTouches");
+ }
+ }
+
@Override
public boolean isInterruptable() {
return true;
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index 92218b1..199db8c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -27,9 +27,8 @@
import com.android.internal.annotations.VisibleForTesting;
-import java.util.ArrayList;
import java.util.Iterator;
-import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Allows clients (such as keyguard) to register for notifications on when biometric lockout
@@ -42,7 +41,7 @@
private final Context mContext;
@VisibleForTesting
- final List<ClientCallback> mClientCallbacks = new ArrayList<>();
+ final ConcurrentLinkedQueue<ClientCallback> mClientCallbacks = new ConcurrentLinkedQueue<>();
private static class ClientCallback {
private static final long WAKELOCK_TIMEOUT_MS = 2000;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8121a63..93d1b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -232,6 +232,7 @@
handleLockout(authenticated);
if (authenticated) {
mState = STATE_STOPPED;
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -268,6 +269,7 @@
// Send the error, but do not invoke the FinishCallback yet. Since lockout is not
// controlled by the HAL, the framework must stop the sensor before finishing the
// client.
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -298,6 +300,7 @@
BiometricNotificationUtils.showBadCalibrationNotification(getContext());
}
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -306,6 +309,7 @@
@Override
protected void startHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.show(getSensorId(), getRequestReason(), this);
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
@@ -419,6 +423,7 @@
@Override
protected void stopHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -518,6 +523,7 @@
Slog.e(TAG, "Remote exception", e);
}
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -548,6 +554,7 @@
Slog.e(TAG, "Remote exception", e);
}
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index cb220b9e..8d2b46f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -87,6 +87,7 @@
@Override
protected void stopHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
unsubscribeBiometricContext();
@@ -102,6 +103,7 @@
@Override
protected void startHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
this);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 225bd59..79975e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -144,6 +144,7 @@
controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
if (remaining == 0) {
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -178,6 +179,7 @@
@Override
public void onError(int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
@@ -192,6 +194,7 @@
@Override
protected void startHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
this);
if (sidefpsControllerRefactor()) {
@@ -273,6 +276,7 @@
@Override
protected void stopHalOperation() {
+ resetIgnoreDisplayTouches();
mSensorOverlays.hide(getSensorId());
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 458fd82..05e681e 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1020,6 +1020,10 @@
}
}
+ private boolean isAutomotive() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
private Set<Integer> getEnabledUserHandles(int currentUserHandle) {
int[] userProfiles = mUserManager.getEnabledProfileIds(currentUserHandle);
Set<Integer> handles = new ArraySet<>(userProfiles.length);
@@ -1030,8 +1034,8 @@
if (Flags.cameraHsumPermission()) {
// If the device is running in headless system user mode then allow
- // User 0 to access camera.
- if (UserManager.isHeadlessSystemUserMode()) {
+ // User 0 to access camera only for automotive form factor.
+ if (UserManager.isHeadlessSystemUserMode() && isAutomotive()) {
handles.add(UserHandle.USER_SYSTEM);
}
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index c260f10..6a6e6ab 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -47,5 +47,19 @@
* @see Configuration#getGrammaticalGender
*/
public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
+
+ /**
+ * Retrieve the system grammatical gender.
+ *
+ * @return the value of grammatical gender
+ *
+ */
+ public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
+ Configuration configuration);
+
+ /**
+ * Whether the package can get the system grammatical gender or not.
+ */
+ public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 6eb7e95..d01f54f 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -22,17 +22,21 @@
import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
import android.os.Binder;
import android.os.Environment;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.os.Trace;
import android.permission.PermissionManager;
import android.util.AtomicFile;
import android.util.Log;
@@ -43,6 +47,7 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -71,6 +76,7 @@
private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
private static final String GRAMMATICAL_INFLECTION_ENABLED =
"i18n.grammatical_Inflection.enabled";
+ private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
private final GrammaticalInflectionBackupHelper mBackupHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -121,16 +127,16 @@
@Override
public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
checkCallerIsSystem();
- checkSystemTermsOfAddressIsEnabled();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
userId);
}
@Override
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- checkSystemTermsOfAddressIsEnabled();
- return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
- userId);
+ return canGetSystemGrammaticalGender(attributionSource)
+ ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
+ attributionSource, userId)
+ : GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
@Override
@@ -159,9 +165,33 @@
@Override
public int getSystemGrammaticalGender(int userId) {
- checkCallerIsSystem();
- return GrammaticalInflectionService.this.getSystemGrammaticalGender(
- mContext.getAttributionSource(), userId);
+ return checkSystemTermsOfAddressIsEnabled()
+ ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
+ mContext.getAttributionSource(), userId)
+ : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ @Override
+ public int retrieveSystemGrammaticalGender(Configuration configuration) {
+ int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId());
+ // Retrieve the grammatical gender from system property, set it into
+ // configuration which will get updated later if the grammatical gender raw value of
+ // current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
+ if (configuration.getGrammaticalGenderRaw()
+ == Configuration.GRAMMATICAL_GENDER_UNDEFINED
+ || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+ Configuration.GRAMMATICAL_GENDER_UNDEFINED);
+ }
+ return systemGrammaticalGender;
+ }
+
+ @Override
+ public boolean canGetSystemGrammaticalGender(int uid, String packageName) {
+ AttributionSource attributionSource = new AttributionSource.Builder(
+ uid).setPackageName(packageName).build();
+ return GrammaticalInflectionService.this.canGetSystemGrammaticalGender(
+ attributionSource);
}
}
@@ -202,11 +232,20 @@
}
protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+ Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
grammaticalGender)) {
throw new IllegalArgumentException("Unknown grammatical gender");
}
+ if (!checkSystemTermsOfAddressIsEnabled()) {
+ if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ return;
+ }
+ Log.d(TAG, "Clearing the system grammatical gender setting");
+ grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
synchronized (mLock) {
final File file = getGrammaticalGenderFile(userId);
final AtomicFile atomicFile = new AtomicFile(file);
@@ -224,6 +263,15 @@
throw new RuntimeException(e);
}
}
+
+ try {
+ Configuration config = new Configuration();
+ config.setGrammaticalGender(grammaticalGender);
+ ActivityTaskManager.getService().updateConfiguration(config);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can not update configuration", e);
+ }
+ Trace.endSection();
}
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -233,34 +281,9 @@
return GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
- int callingUid = Binder.getCallingUid();
- if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
- Log.d(TAG,
- "Package " + packageName + " does not belong to the calling uid " + callingUid);
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
- }
-
- if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
- }
-
synchronized (mLock) {
- final File file = getGrammaticalGenderFile(userId);
- if (!file.exists()) {
- Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
- return GRAMMATICAL_GENDER_NOT_SPECIFIED;
- }
-
- if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
- try {
- InputStream in = new FileInputStream(file);
- final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Failed to parse XML configuration from " + file, e);
- }
- }
- return mGrammaticalGenderCache.get(userId);
+ int grammaticalGender = mGrammaticalGenderCache.get(userId);
+ return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
}
}
@@ -311,9 +334,39 @@
}
}
- private void checkSystemTermsOfAddressIsEnabled() {
+ private boolean checkSystemTermsOfAddressIsEnabled() {
if (!systemTermsOfAddressEnabled()) {
- throw new RuntimeException("The flag must be enabled to allow calling the API.");
+ Log.d(TAG, "The flag must be enabled to allow calling the API.");
+ return false;
}
+ return true;
+ }
+
+ private boolean canGetSystemGrammaticalGender(AttributionSource attributionSource) {
+ return checkSystemTermsOfAddressIsEnabled() && checkSystemGrammaticalGenderPermission(
+ mPermissionManager, attributionSource);
+ }
+
+ @Override
+ public void onUserUnlocked(TargetUser user) {
+ IoThread.getHandler().post(() -> {
+ int userId = user.getUserIdentifier();
+ final File file = getGrammaticalGenderFile(userId);
+ synchronized (mLock) {
+ if (!file.exists()) {
+ Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ return;
+ }
+ if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+ try {
+ InputStream in = new FileInputStream(file);
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+ }
+ }
+ });
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e0e825d..c8c66238 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1366,6 +1366,12 @@
// we don't call onInitializeCecComplete()
// since we reallocate the logical address only.
onInitializeCecComplete(initiatedBy);
+ } else if (initiatedBy == INITIATED_BY_HOTPLUG
+ && mDisplayStatusCallback == null) {
+ // Force to update display status for hotplug event.
+ synchronized (mLock) {
+ announceHdmiControlStatusChange(mHdmiControlEnabled);
+ }
}
// We remove local devices here, instead of before the start of
// address allocation, to prevent multiple local devices of the
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7726609..574be34 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -621,10 +621,6 @@
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
mKeyRemapper.systemRunning();
-
- mNative.setStylusPointerIconEnabled(
- Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .isStylusPointerIconEnabled());
}
private void reloadDeviceAliases() {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 5ffc380..c02d524 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -94,7 +94,9 @@
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
(reason) -> updateAccessibilitySlowKeys()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
- (reason) -> updateAccessibilityStickyKeys()));
+ (reason) -> updateAccessibilityStickyKeys()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.STYLUS_POINTER_ICON_ENABLED),
+ (reason) -> updateStylusPointerIconEnabled()));
}
/**
@@ -254,4 +256,8 @@
mNative.setMinTimeBetweenUserActivityPokes(intervalMillis);
}
}
+
+ private void updateStylusPointerIconEnabled() {
+ mNative.setStylusPointerIconEnabled(InputSettings.isStylusPointerIconEnabled(mContext));
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 91706cf..7dbe880 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -727,7 +727,7 @@
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
- private static final IBinder ALLOWLIST_TOKEN = new Binder();
+ static final IBinder ALLOWLIST_TOKEN = new Binder();
protected RankingHandler mRankingHandler;
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -4759,7 +4759,7 @@
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.clearAllowlistToken();
+ notification.overrideAllowlistToken(null);
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -7623,6 +7623,8 @@
}
}
+ notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
+
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
new file mode 100644
index 0000000..1553618
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.pm;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import android.annotation.NonNull;
+import android.app.BackgroundInstallControlManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IRemoteCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+public class BackgroundInstallControlCallbackHelper {
+
+ @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+ @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
+ private static final String TAG = "BackgroundInstallControlCallbackHelper";
+
+ private final Handler mHandler;
+
+ BackgroundInstallControlCallbackHelper() {
+ HandlerThread backgroundThread =
+ new ServiceThread(
+ "BackgroundInstallControlCallbackHelperBg",
+ THREAD_PRIORITY_BACKGROUND,
+ true);
+ backgroundThread.start();
+ mHandler = new Handler(backgroundThread.getLooper());
+ }
+
+ @NonNull @VisibleForTesting
+ final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+ /** Registers callback that gets invoked upon detection of an MBA
+ *
+ * NOTE: The callback is user context agnostic and currently broadcasts to all users of other
+ * users app installs. This is fine because the API is for SystemServer use only.
+ */
+ public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.register(callback, null);
+ }
+ }
+
+ /** Unregisters callback */
+ public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.unregister(callback);
+ }
+ }
+
+ /**
+ * Invokes all registered callbacks Callbacks are processed through user provided-threads and
+ * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
+ */
+ public void notifyAllCallbacks(int userId, String packageName) {
+ Bundle extras = new Bundle();
+ extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
+ extras.putInt(FLAGGED_USER_ID_KEY, userId);
+ synchronized (mCallbacks) {
+ mHandler.post(
+ () ->
+ mCallbacks.broadcast(
+ callback -> {
+ try {
+ callback.sendResult(extras);
+ } catch (RemoteException e) {
+ Slog.e(
+ TAG,
+ "error detected: " + e.getLocalizedMessage(),
+ e);
+ }
+ }));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 200b17b..3468081 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -36,6 +36,7 @@
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
@@ -93,6 +94,8 @@
private final File mDiskFile;
private final Context mContext;
+ private final BackgroundInstallControlCallbackHelper mCallbackHelper;
+
private SparseSetArray<String> mBackgroundInstalledPackages = null;
// User ID -> package name -> set of foreground time frame
@@ -112,6 +115,7 @@
mHandler = new EventHandler(injector.getLooper(), this);
mDiskFile = injector.getDiskFile();
mContext = injector.getContext();
+ mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper();
UsageStatsManagerInternal usageStatsManagerInternal =
injector.getUsageStatsManagerInternal();
usageStatsManagerInternal.registerListener(
@@ -150,6 +154,16 @@
}
}
+ @Override
+ public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+ mService.mCallbackHelper.registerBackgroundInstallCallback(callback);
+ }
+
+ @Override
+ public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+ mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback);
+ }
+
}
@RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
@@ -274,6 +288,7 @@
initBackgroundInstalledPackages();
mBackgroundInstalledPackages.add(userId, packageName);
+ mCallbackHelper.notifyAllCallbacks(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
}
@@ -568,6 +583,8 @@
File getDiskFile();
+ BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper();
+
}
private static final class InjectorImpl implements Injector {
@@ -617,5 +634,10 @@
File file = new File(dir, DISK_FILE_NAME);
return file;
}
+
+ @Override
+ public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+ return new BackgroundInstallControlCallbackHelper();
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 33f481c..db5acc2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -592,9 +592,11 @@
mPm.addAllPackageProperties(pkg);
if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
- mPm.mDomainVerificationManager.addPackage(pkgSetting);
+ mPm.mDomainVerificationManager.addPackage(pkgSetting,
+ request.getPreVerifiedDomains());
} else {
- mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+ mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting,
+ request.getPreVerifiedDomains());
}
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8960da..afd4fb1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -569,7 +569,8 @@
* target sdk apps as malware can target older sdk versions to avoid
* the enforcement of new API behavior.
*/
- public static final int MIN_INSTALLABLE_TARGET_SDK = Build.VERSION_CODES.M;
+ public static final int MIN_INSTALLABLE_TARGET_SDK =
+ Flags.minTargetSdk24() ? Build.VERSION_CODES.N : Build.VERSION_CODES.M;
// Compilation reasons.
// TODO(b/260124949): Clean this up with the legacy dexopt code.
@@ -6483,6 +6484,17 @@
}
@Override
+ @Nullable
+ public ComponentName getDomainVerificationAgent() {
+ final int callerUid = Binder.getCallingUid();
+ if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
+ throw new SecurityException("Not allowed to query domain verification agent");
+ }
+ final Computer snapshot = snapshotComputer();
+ return getDomainVerificationAgentComponentNameLPr(snapshot);
+ }
+
+ @Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e329f09..89589ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -396,6 +396,8 @@
return runArchive();
case "request-unarchive":
return runUnarchive();
+ case "get-domain-verification-agent":
+ return runGetDomainVerificationAgent();
default: {
if (ART_SERVICE_COMMANDS.contains(cmd)) {
if (DexOptHelper.useArtService()) {
@@ -4794,6 +4796,19 @@
return 0;
}
+ private int runGetDomainVerificationAgent() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent();
+ pw.println(domainVerificationAgent == null
+ ? "No Domain Verifier available!" : domainVerificationAgent.toString());
+ } catch (Exception e) {
+ pw.println("Failure [" + e.getMessage() + "]");
+ return 1;
+ }
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -5194,6 +5209,9 @@
pw.println(" to unarchive an app to the responsible installer. Options are:");
pw.println(" --user: request unarchival of the app from the given user.");
pw.println("");
+ pw.println(" get-domain-verification-agent");
+ pw.println(" Displays the component name of the domain verification agent on device.");
+ pw.println("");
if (DexOptHelper.useArtService()) {
printArtServiceHelp();
} else {
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index e9c6aab..b35f9c2 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -209,9 +209,8 @@
if (ret == null) {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
- } else {
- ret.attemptToRestoreIfNeededAndSave();
}
+ ret.attemptToRestoreIfNeededAndSave();
return ret;
}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b720304..067a012 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -288,29 +288,6 @@
* configuration.
*/
private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
- UserProperties.Builder userPropertiesBuilder = new UserProperties.Builder()
- .setStartWithParent(true)
- .setCredentialShareableWithParent(true)
- .setAuthAlwaysRequiredToDisableQuietMode(true)
- .setAllowStoppingUserWithDelayedLocking(true)
- .setMediaSharedWithParent(false)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
- .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
- .setShowInQuietMode(
- UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
- .setShowInSharingSurfaces(
- UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
- .setCrossProfileIntentFilterAccessControl(
- UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
- .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
- .setCrossProfileContentSharingStrategy(
- UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
- .setItemsRestrictedOnHomeScreen(true);
- if (android.multiuser.Flags.supportHidingProfiles()) {
- userPropertiesBuilder.setProfileApiVisibility(
- UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
- }
-
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_PRIVATE)
.setBaseType(FLAG_PROFILE)
@@ -329,7 +306,26 @@
.setDarkThemeBadgeColors(
R.color.white)
.setDefaultRestrictions(getDefaultProfileRestrictions())
- .setDefaultUserProperties(userPropertiesBuilder);
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
+ .setAllowStoppingUserWithDelayedLocking(true)
+ .setMediaSharedWithParent(false)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setShowInQuietMode(
+ UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ .setShowInSharingSurfaces(
+ UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+ .setCrossProfileIntentFilterAccessControl(
+ UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+ .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .setCrossProfileContentSharingStrategy(
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+ .setProfileApiVisibility(
+ UserProperties.PROFILE_API_VISIBILITY_HIDDEN)
+ .setItemsRestrictedOnHomeScreen(true));
}
/**
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a9e5a54..1c70af0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -594,6 +594,7 @@
}
ai.applicationInfo = applicationInfo;
ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+ ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller();
ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
return ai;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 53ee189..7ca449a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -26,6 +26,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
@@ -230,13 +231,20 @@
* broadcast will be sent to the domain verification agent so it may re-run any verification
* logic for the newly associated domains.
* <p>
- * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
- * lock. This should never be called from within the domain verification classes themselves.
+ * Optionally, the caller can specify a set of domains that are already pre-verified by the
+ * installer. These domains, if specified with autoVerify in the manifest, will be regarded as
+ * verified as soon as the app is installed, until the domain verification agent sends back the
+ * real verification results.
+ * <p>
+ * This method will mutate internal {@link DomainVerificationPkgState} and so will hold the
+ * internal lock. This should never be called from within the domain verification classes
+ * themselves.
* <p>
* This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
* handled by the caller.
*/
- void addPackage(@NonNull PackageStateInternal newPkgSetting);
+ void addPackage(@NonNull PackageStateInternal newPkgSetting,
+ @Nullable DomainSet preVerifiedDomains);
/**
* Migrates verification state from a previous install to a new one. It is expected that the
@@ -245,14 +253,20 @@
* domains under the assumption that the new package will pass the same server side config as
* the previous package, as they have matching signatures.
* <p>
- * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
- * lock. This should never be called from within the domain verification classes themselves.
+ * Optionally, the caller can specify a set of domains that are already pre-verified by the
+ * installer. These domains, if specified with autoVerify in the manifest, will be regarded as
+ * verified as soon as the app is updated, until the domain verification agent sends back the
+ * real verification results.
+ * <p>
+ * This method will mutate internal {@link DomainVerificationPkgState} and so will hold the
+ * internal lock. This should never be called from within the domain verification classes
+ * themselves.
* <p>
* This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
* handled by the caller.
*/
void migrateState(@NonNull PackageStateInternal oldPkgSetting,
- @NonNull PackageStateInternal newPkgSetting);
+ @NonNull PackageStateInternal newPkgSetting, @Nullable DomainSet preVerifiedDomains);
/**
* Serializes the entire internal state. This is equivalent to a full backup of the existing
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 6150099..c796b40 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainOwner;
+import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
@@ -859,7 +860,7 @@
@Override
public void migrateState(@NonNull PackageStateInternal oldPkgSetting,
- @NonNull PackageStateInternal newPkgSetting) {
+ @NonNull PackageStateInternal newPkgSetting, @Nullable DomainSet preVerifiedDomains) {
String pkgName = newPkgSetting.getPackageName();
boolean sendBroadcast;
@@ -935,6 +936,9 @@
sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
+ // Apply pre-verified states as the last step of migration
+ applyPreVerifiedState(newStateMap, newAutoVerifyDomains, preVerifiedDomains);
+
mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates,
null /* signature */));
@@ -947,7 +951,8 @@
// TODO(b/159952358): Handle valid domainSetIds for PackageStateInternals with no AndroidPackage
@Override
- public void addPackage(@NonNull PackageStateInternal newPkgSetting) {
+ public void addPackage(@NonNull PackageStateInternal newPkgSetting,
+ @Nullable DomainSet preVerifiedDomains) {
// TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
// the state map, but it would require handling the "migration" case where an app either
// gains or loses all domains.
@@ -1029,6 +1034,9 @@
DomainVerificationState.STATE_MIGRATED);
}
}
+
+ // Apply pre-verified states before sending out broadcast
+ applyPreVerifiedState(pkgState.getStateMap(), autoVerifyDomains, preVerifiedDomains);
}
synchronized (mLock) {
@@ -1040,6 +1048,27 @@
}
}
+ private void applyPreVerifiedState(ArrayMap<String, Integer> stateMap,
+ ArraySet<String> autoVerifyDomains,
+ DomainSet preVerifiedDomains) {
+ // If any pre-verified domains are provided, treating them as verified as well. This
+ // allows the app to be opened immediately by the corresponding app links, but the
+ // pre-verified state can still be overwritten by the domain verification agent in the
+ // future.
+ if (preVerifiedDomains != null && !autoVerifyDomains.isEmpty()) {
+ for (String preVerifiedDomain : preVerifiedDomains.getDomains()) {
+ if (autoVerifyDomains.contains(preVerifiedDomain)
+ && !stateMap.containsKey(preVerifiedDomain)) {
+ // Only set the pre-verified state if there's no existing state
+ stateMap.put(preVerifiedDomain, DomainVerificationState.STATE_PRE_VERIFIED);
+ if (DEBUG_APPROVAL) {
+ Slog.d(TAG, "Inserted pre-verified domain: " + preVerifiedDomain);
+ }
+ }
+ }
+ }
+ }
+
/**
* Applies any immutable state as the final step when adding or migrating state. Currently only
* applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a system app.
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 466c4c9..13b072b 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -62,6 +62,7 @@
pw.println(" - restored: preserved verification from a user data restore");
pw.println(" - legacy_failure: rejected by a legacy verifier, unknown reason");
pw.println(" - system_configured: automatically approved by the device config");
+ pw.println(" - pre_verified: the domain was pre-verified by the installer");
pw.println(" - >= 1024: Custom error code which is specific to the device verifier");
pw.println(" --user <USER_ID>: include user selections (includes all domains, not");
pw.println(" just autoVerify ones)");
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 03c75e0..195e91c 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.IBinder;
@@ -63,6 +64,15 @@
String targetPkg, int targetUserId);
/**
+ * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an
+ * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link
+ * android.R.attr#requireContentUriPermissionFromCaller} attribute.
+ */
+ NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+ String targetPkg, int targetUserId,
+ @RequiredContentUriPermission int requireContentUriPermissionFromCaller);
+
+ /**
* Extend a previously calculated set of permissions grants to the given
* owner. All security checks will have already been performed as part of
* calculating {@link NeededUriGrants}.
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index ce2cbed..d2f6701 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -23,6 +23,10 @@
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
@@ -53,6 +57,8 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -609,7 +615,8 @@
/** Like checkGrantUriPermission, but takes an Intent. */
private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid,
- String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
+ String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId,
+ @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) {
if (DEBUG) Slog.v(TAG,
"Checking URI perm to data=" + (intent != null ? intent.getData() : null)
+ " clip=" + (intent != null ? intent.getClipData() : null)
@@ -647,6 +654,10 @@
}
if (data != null) {
GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
+ if (android.security.Flags.contentUriPermissionApis()) {
+ enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller,
+ grantUri, callingUid);
+ }
targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode,
targetUid);
if (targetUid > 0) {
@@ -661,6 +672,10 @@
Uri uri = clip.getItemAt(i).getUri();
if (uri != null) {
GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
+ if (android.security.Flags.contentUriPermissionApis()) {
+ enforceRequireContentUriPermissionFromCaller(
+ requireContentUriPermissionFromCaller, grantUri, callingUid);
+ }
targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg,
grantUri, mode, targetUid);
if (targetUid > 0) {
@@ -673,7 +688,8 @@
Intent clipIntent = clip.getItemAt(i).getIntent();
if (clipIntent != null) {
NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked(
- callingUid, targetPkg, clipIntent, mode, needed, targetUserId);
+ callingUid, targetPkg, clipIntent, mode, needed, targetUserId,
+ requireContentUriPermissionFromCaller);
if (newNeeded != null) {
needed = newNeeded;
}
@@ -685,6 +701,38 @@
return needed;
}
+ private void enforceRequireContentUriPermissionFromCaller(
+ @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+ GrantUri grantUri, int uid) {
+ // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a
+ // non-content URI.
+ if (requireContentUriPermissionFromCaller == null
+ || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE
+ || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+ return;
+ }
+
+ final boolean readMet = !isRequiredContentUriPermissionRead(
+ requireContentUriPermissionFromCaller)
+ || checkContentUriPermissionFullUnlocked(grantUri, uid,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ final boolean writeMet = !isRequiredContentUriPermissionWrite(
+ requireContentUriPermissionFromCaller)
+ || checkContentUriPermissionFullUnlocked(grantUri, uid,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ boolean hasPermission =
+ requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE
+ ? (readMet || writeMet) : (readMet && writeMet);
+
+ if (!hasPermission) {
+ throw new SecurityException("You can't launch this activity because you don't have the"
+ + " required " + ActivityInfo.requiredContentUriPermissionToShortString(
+ requireContentUriPermissionFromCaller) + " access to " + grantUri.uri);
+ }
+ }
+
@GuardedBy("mLock")
private void readGrantedUriPermissionsLocked() {
if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()");
@@ -1560,9 +1608,24 @@
@Override
public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
String targetPkg, int targetUserId) {
+ return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+ targetUserId, /* requireContentUriPermissionFromCaller */ null);
+ }
+
+ @Override
+ public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+ String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) {
+ return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+ targetUserId, requireContentUriPermissionFromCaller);
+ }
+
+ private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent,
+ int callingUid, String targetPkg, int targetUserId,
+ @Nullable Integer requireContentUriPermissionFromCaller) {
final int mode = (intent != null) ? intent.getFlags() : 0;
return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked(
- callingUid, targetPkg, intent, mode, null, targetUserId);
+ callingUid, targetPkg, intent, mode, null, targetUserId,
+ requireContentUriPermissionFromCaller);
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 8f8fe3c..17a9e33 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -248,8 +248,6 @@
IVibratorController vibratorController =
mVibratorControllerHolder.getVibratorController();
if (vibratorController == null) {
- Slog.d(TAG, "Unable to check if should request vibration params. "
- + "There is no registered IVibrationController.");
return false;
}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index b2bbcda..88d3daf 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -32,6 +32,8 @@
import com.android.internal.infra.ServiceConnector;
+import java.io.IOException;
+
/** Manages the connection to the remote wearable sensing service. */
final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
private static final String TAG =
@@ -56,6 +58,29 @@
}
/**
+ * Provides a secure connection to the wearable.
+ *
+ * @param secureWearableConnection The secure connection to the wearable
+ * @param callback The callback for service status
+ */
+ public void provideSecureWearableConnection(
+ ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Providing secure wearable connection.");
+ }
+ var unused = post(
+ service -> {
+ service.provideSecureWearableConnection(secureWearableConnection, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ secureWearableConnection.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
+ }
+
+ /**
* Provides the implementation a data stream to the wearable.
*
* @param parcelFileDescriptor The data stream to the wearable
@@ -66,7 +91,16 @@
if (DEBUG) {
Slog.i(TAG, "Providing data stream.");
}
- post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+ var unused = post(
+ service -> {
+ service.provideDataStream(parcelFileDescriptor, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ parcelFileDescriptor.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
}
/**
@@ -84,4 +118,62 @@
}
post(service -> service.provideData(data, sharedMemory, callback));
}
+
+ /**
+ * Registers a data request observer with WearableSensingService.
+ *
+ * @param dataType The data type to listen to. Values are defined by the application that
+ * implements WearableSensingService.
+ * @param dataRequestCallback The observer to send data requests to.
+ * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+ * unregistering the observer.
+ * @param packageName The package name of the app that will receive the data requests.
+ * @param statusCallback The callback for status of the method call.
+ */
+ public void registerDataRequestObserver(
+ int dataType,
+ RemoteCallback dataRequestCallback,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Registering data request observer.");
+ }
+ var unused =
+ post(
+ service ->
+ service.registerDataRequestObserver(
+ dataType,
+ dataRequestCallback,
+ dataRequestObserverId,
+ packageName,
+ statusCallback));
+ }
+
+ /**
+ * Unregisters a previously registered data request observer.
+ *
+ * @param dataType The data type the observer was registered against.
+ * @param dataRequestObserverId The unique ID of the observer to unregister.
+ * @param packageName The package name of the app that will receive requests sent to the
+ * observer.
+ * @param statusCallback The callback for status of the method call.
+ */
+ public void unregisterDataRequestObserver(
+ int dataType,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Unregistering data request observer.");
+ }
+ var unused =
+ post(
+ service ->
+ service.unregisterDataRequestObserver(
+ dataType,
+ dataRequestObserverId,
+ packageName,
+ statusCallback));
+ }
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index e73fd0f..0e8b82f 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -22,17 +22,19 @@
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.Flags;
import android.app.wearable.WearableSensingManager;
+import android.companion.CompanionDeviceManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
-import android.system.OsConstants;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SharedMemory;
+import android.system.OsConstants;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -40,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.infra.AbstractPerUserSystemService;
+import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -55,6 +58,10 @@
RemoteWearableSensingService mRemoteService;
private ComponentName mComponentName;
+ private final Object mSecureChannelLock = new Object();
+
+ @GuardedBy("mSecureChannelLock")
+ private WearableSensingSecureChannel mSecureChannel;
WearableSensingManagerPerUserService(
@NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
@@ -76,6 +83,11 @@
mRemoteService = null;
}
}
+ synchronized (mSecureChannelLock) {
+ if (mSecureChannel != null) {
+ mSecureChannel.close();
+ }
+ }
}
@GuardedBy("mLock")
@@ -156,6 +168,63 @@
}
/**
+ * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
+ * service.
+ */
+ public void onProvideWearableConnection(
+ ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ Slog.i(TAG, "onProvideWearableConnection in per user service.");
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ }
+ synchronized (mSecureChannelLock) {
+ if (mSecureChannel != null) {
+ // TODO(b/321012559): Kill the WearableSensingService process if it has not been
+ // killed from onError
+ mSecureChannel.close();
+ }
+ try {
+ mSecureChannel =
+ WearableSensingSecureChannel.create(
+ getContext().getSystemService(CompanionDeviceManager.class),
+ wearableConnection,
+ new WearableSensingSecureChannel.SecureTransportListener() {
+ @Override
+ public void onSecureTransportAvailable(
+ ParcelFileDescriptor secureTransport) {
+ Slog.i(TAG, "calling over to remote service.");
+ synchronized (mLock) {
+ ensureRemoteServiceInitiated();
+ mRemoteService.provideSecureWearableConnection(
+ secureTransport, callback);
+ }
+ }
+
+ @Override
+ public void onError() {
+ // TODO(b/321012559): Kill the WearableSensingService
+ // process if mSecureChannel has not been reassigned
+ if (Flags.enableProvideWearableConnectionApi()) {
+ notifyStatusCallback(
+ callback,
+ WearableSensingManager.STATUS_CHANNEL_ERROR);
+ }
+ }
+ });
+ } catch (IOException ex) {
+ Slog.e(TAG, "Unable to create the secure channel.", ex);
+ if (Flags.enableProvideWearableConnectionApi()) {
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
* Handles sending the provided data stream for the wearable to the wearable sensing service.
*/
public void onProvideDataStream(
@@ -193,4 +262,65 @@
mRemoteService.provideData(data, sharedMemory, callback);
}
}
+
+ /**
+ * Handles registering a data request observer.
+ *
+ * @param dataType The data type to listen to. Values are defined by the application that
+ * implements WearableSensingService.
+ * @param dataRequestObserver The observer to register.
+ * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+ * unregistering the observer.
+ * @param packageName The package name of the app that will receive the data requests.
+ * @param statusCallback The callback for status of the method call.
+ */
+ public void onRegisterDataRequestObserver(
+ int dataType,
+ RemoteCallback dataRequestObserver,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ ensureRemoteServiceInitiated();
+ mRemoteService.registerDataRequestObserver(
+ dataType,
+ dataRequestObserver,
+ dataRequestObserverId,
+ packageName,
+ statusCallback);
+ }
+ }
+
+ /**
+ * Handles unregistering a previously registered data request observer.
+ *
+ * @param dataType The data type the observer was registered against.
+ * @param dataRequestObserverId The unique ID of the observer to unregister.
+ * @param packageName The package name of the app that will receive requests sent to the
+ * observer.
+ * @param statusCallback The callback for status of the method call.
+ */
+ public void onUnregisterDataRequestObserver(
+ int dataType,
+ int dataRequestObserverId,
+ String packageName,
+ RemoteCallback statusCallback) {
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ ensureRemoteServiceInitiated();
+ mRemoteService.unregisterDataRequestObserver(
+ dataType, dataRequestObserverId, packageName, statusCallback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 4cc2c02..78952fa 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -21,12 +21,18 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
+import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingDataRequest;
import android.app.wearable.WearableSensingManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -35,6 +41,8 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.service.wearable.WearableSensingDataRequester;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.R;
@@ -44,10 +52,13 @@
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import com.android.server.pm.KnownPackages;
+import com.android.server.utils.quota.MultiRateLimiter;
import java.io.FileDescriptor;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
@@ -64,9 +75,38 @@
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;
+ private static final String RATE_LIMITER_PACKAGE_NAME = "android";
+ private static final String RATE_LIMITER_TAG =
+ WearableSensingManagerService.class.getSimpleName();
+
+ private static final class DataRequestObserverContext {
+ final int mDataType;
+ final int mUserId;
+ final int mDataRequestObserverId;
+ @NonNull final PendingIntent mDataRequestPendingIntent;
+ @NonNull final RemoteCallback mDataRequestRemoteCallback;
+
+ DataRequestObserverContext(
+ int dataType,
+ int userId,
+ int dataRequestObserverId,
+ PendingIntent dataRequestPendingIntent,
+ RemoteCallback dataRequestRemoteCallback) {
+ mDataType = dataType;
+ mUserId = userId;
+ mDataRequestObserverId = dataRequestObserverId;
+ mDataRequestPendingIntent = dataRequestPendingIntent;
+ mDataRequestRemoteCallback = dataRequestRemoteCallback;
+ }
+ }
+
private final Context mContext;
+ private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1);
+ private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>();
+ private final MultiRateLimiter mDataRequestRateLimiter;
volatile boolean mIsServiceEnabled;
public WearableSensingManagerService(Context context) {
@@ -78,6 +118,12 @@
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
+ mDataRequestRateLimiter =
+ new MultiRateLimiter.Builder(context)
+ .addRateLimit(
+ WearableSensingDataRequest.getRateLimit(),
+ WearableSensingDataRequest.getRateLimitWindowSize())
+ .build();
}
@Override
@@ -192,6 +238,96 @@
}
}
+ private DataRequestObserverContext getDataRequestObserverContext(
+ int dataType, int userId, PendingIntent dataRequestPendingIntent) {
+ synchronized (mDataRequestObserverContexts) {
+ for (DataRequestObserverContext observerContext : mDataRequestObserverContexts) {
+ if (observerContext.mDataType == dataType
+ && observerContext.mUserId == userId
+ && observerContext.mDataRequestPendingIntent.equals(
+ dataRequestPendingIntent)) {
+ return observerContext;
+ }
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ private RemoteCallback createDataRequestRemoteCallback(
+ PendingIntent dataRequestPendingIntent, int userId) {
+ return new RemoteCallback(
+ bundle -> {
+ WearableSensingDataRequest dataRequest =
+ bundle.getParcelable(
+ WearableSensingDataRequest.REQUEST_BUNDLE_KEY,
+ WearableSensingDataRequest.class);
+ if (dataRequest == null) {
+ Slog.e(TAG, "Received data request callback without a request.");
+ return;
+ }
+ RemoteCallback dataRequestStatusCallback =
+ bundle.getParcelable(
+ WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+ RemoteCallback.class);
+ if (dataRequestStatusCallback == null) {
+ Slog.e(TAG, "Received data request callback without a status callback.");
+ return;
+ }
+ if (dataRequest.getDataSize()
+ > WearableSensingDataRequest.getMaxRequestSize()) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "WearableSensingDataRequest size exceeds the maximum"
+ + " allowed size of %s bytes. Dropping the request.",
+ WearableSensingDataRequest.getMaxRequestSize()));
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ dataRequestStatusCallback,
+ WearableSensingDataRequester.STATUS_TOO_LARGE);
+ return;
+ }
+ if (!mDataRequestRateLimiter.isWithinQuota(
+ userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG)) {
+ Slog.w(TAG, "Data request exceeded rate limit. Dropping the request.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ dataRequestStatusCallback,
+ WearableSensingDataRequester.STATUS_TOO_FREQUENT);
+ return;
+ }
+ Intent intent = new Intent();
+ intent.putExtra(
+ WearableSensingManager.EXTRA_WEARABLE_SENSING_DATA_REQUEST,
+ dataRequest);
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ mDataRequestRateLimiter.noteEvent(
+ userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG);
+ final long previousCallingIdentity = Binder.clearCallingIdentity();
+ try {
+ dataRequestPendingIntent.send(
+ getContext(), 0, intent, null, null, null, options.toBundle());
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ dataRequestStatusCallback,
+ WearableSensingDataRequester.STATUS_SUCCESS);
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "Sending data request to %s: %s",
+ dataRequestPendingIntent.getCreatorPackage(),
+ dataRequest.toExpandedString()));
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Could not deliver pendingIntent: " + dataRequestPendingIntent);
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ dataRequestStatusCallback,
+ WearableSensingDataRequester.STATUS_OBSERVER_CANCELLED);
+ } finally {
+ Binder.restoreCallingIdentity(previousCallingIdentity);
+ }
+ });
+ }
+
private void callPerUserServiceIfExist(
Consumer<WearableSensingManagerPerUserService> serviceConsumer,
RemoteCallback statusCallback) {
@@ -211,9 +347,27 @@
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
@Override
+ public void provideWearableConnection(
+ ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection.");
+ Objects.requireNonNull(wearableConnection);
+ Objects.requireNonNull(callback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ callPerUserServiceIfExist(
+ service -> service.onProvideWearableConnection(wearableConnection, callback),
+ callback);
+ }
+
+ @Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor,
- RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
Objects.requireNonNull(parcelFileDescriptor);
Objects.requireNonNull(callback);
@@ -242,8 +396,8 @@
Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available.");
- WearableSensingManagerPerUserService.notifyStatusCallback(callback,
- WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
callPerUserServiceIfExist(
@@ -252,6 +406,96 @@
}
@Override
+ public void registerDataRequestObserver(
+ int dataType,
+ PendingIntent dataRequestPendingIntent,
+ RemoteCallback statusCallback) {
+ Slog.i(TAG, "WearableSensingManagerInternal registerDataRequestObserver.");
+ Objects.requireNonNull(dataRequestPendingIntent);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ int userId = UserHandle.getCallingUserId();
+ RemoteCallback dataRequestCallback;
+ int dataRequestObserverId;
+ synchronized (mDataRequestObserverContexts) {
+ DataRequestObserverContext previousObserverContext =
+ getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+ if (previousObserverContext != null) {
+ Slog.i(TAG, "Received duplicate data request observer.");
+ dataRequestCallback = previousObserverContext.mDataRequestRemoteCallback;
+ dataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+ } else {
+ dataRequestCallback =
+ createDataRequestRemoteCallback(dataRequestPendingIntent, userId);
+ dataRequestObserverId = mNextDataRequestObserverId.getAndIncrement();
+ mDataRequestObserverContexts.add(
+ new DataRequestObserverContext(
+ dataType,
+ userId,
+ dataRequestObserverId,
+ dataRequestPendingIntent,
+ dataRequestCallback));
+ }
+ }
+ callPerUserServiceIfExist(
+ service ->
+ service.onRegisterDataRequestObserver(
+ dataType,
+ dataRequestCallback,
+ dataRequestObserverId,
+ dataRequestPendingIntent.getCreatorPackage(),
+ statusCallback),
+ statusCallback);
+ }
+
+ @Override
+ public void unregisterDataRequestObserver(
+ int dataType,
+ PendingIntent dataRequestPendingIntent,
+ RemoteCallback statusCallback) {
+ Slog.i(TAG, "WearableSensingManagerInternal unregisterDataRequestObserver.");
+ Objects.requireNonNull(dataRequestPendingIntent);
+ Objects.requireNonNull(statusCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ int userId = UserHandle.getCallingUserId();
+ int previousDataRequestObserverId;
+ String pendingIntentCreatorPackage;
+ synchronized (mDataRequestObserverContexts) {
+ DataRequestObserverContext previousObserverContext =
+ getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+ if (previousObserverContext == null) {
+ Slog.w(TAG, "Previous observer not found, cannot unregister.");
+ return;
+ }
+ mDataRequestObserverContexts.remove(previousObserverContext);
+ previousDataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+ pendingIntentCreatorPackage =
+ previousObserverContext.mDataRequestPendingIntent.getCreatorPackage();
+ }
+ callPerUserServiceIfExist(
+ service ->
+ service.onUnregisterDataRequestObserver(
+ dataType,
+ previousDataRequestObserverId,
+ pendingIntentCreatorPackage,
+ statusCallback),
+ statusCallback);
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
new file mode 100644
index 0000000..a16ff51
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -0,0 +1,350 @@
+/*
+ * 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.wearable;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing.
+ *
+ * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the
+ * connection to the CompanionDeviceManager via {@link
+ * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will
+ * create an encrypted channel using the provided connection as the raw underlying connection. The
+ * wearable device is expected to attach its side of the raw connection to its
+ * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two
+ * devices can perform attestation and set up the encrypted channel. Attestation requirements are
+ * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}.
+ *
+ * <p>When the encrypted channel is available, it will be provided to the caller via the
+ * SecureTransportListener.
+ */
+final class WearableSensingSecureChannel {
+
+ /** A listener for secure transport and its error signal. */
+ interface SecureTransportListener {
+
+ /** Called when the secure transport is available. */
+ void onSecureTransportAvailable(ParcelFileDescriptor secureTransport);
+
+ /**
+ * Called when there is a non-recoverable error. The secure channel will be automatically
+ * closed.
+ */
+ void onError();
+ }
+
+ private static final String TAG = WearableSensingSecureChannel.class.getSimpleName();
+ private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM";
+ // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener
+ private static final int READ_BUFFER_SIZE = 8192;
+
+ private final Object mLock = new Object();
+ // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the
+ // corresponding cleanup methods in CDM have been called (e.g.
+ // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after
+ // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions.
+ private final SoftShutdownExecutor mMessageFromWearableExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final SoftShutdownExecutor mMessageToWearableExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final SoftShutdownExecutor mLightWeightExecutor =
+ new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+ private final CompanionDeviceManager mCompanionDeviceManager;
+ private final ParcelFileDescriptor mUnderlyingTransport;
+ private final SecureTransportListener mSecureTransportListener;
+ private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false);
+ private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener =
+ this::onTransportsChanged;
+ private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived;
+ private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener
+ // read input received from the ParcelFileDescriptor returned to mSecureTransportListener
+ private final InputStream mLocalIn;
+ // send output to the ParcelFileDescriptor returned to mSecureTransportListener
+ private final OutputStream mLocalOut;
+
+ @GuardedBy("mLock")
+ private boolean mClosed = false;
+
+ private Integer mAssociationId = null;
+
+ /**
+ * Creates a WearableSensingSecureChannel. When the secure transport is ready,
+ * secureTransportListener will be notified.
+ *
+ * @param companionDeviceManager The CompanionDeviceManager system service.
+ * @param underlyingTransport The underlying transport to create the secure channel on.
+ * @param secureTransportListener The listener to receive the secure transport when it is ready.
+ * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair.
+ */
+ static WearableSensingSecureChannel create(
+ @NonNull CompanionDeviceManager companionDeviceManager,
+ @NonNull ParcelFileDescriptor underlyingTransport,
+ @NonNull SecureTransportListener secureTransportListener)
+ throws IOException {
+ Objects.requireNonNull(companionDeviceManager);
+ Objects.requireNonNull(underlyingTransport);
+ Objects.requireNonNull(secureTransportListener);
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
+ WearableSensingSecureChannel channel =
+ new WearableSensingSecureChannel(
+ companionDeviceManager,
+ underlyingTransport,
+ secureTransportListener,
+ pair[0],
+ pair[1]);
+ channel.initialize();
+ return channel;
+ }
+
+ private WearableSensingSecureChannel(
+ CompanionDeviceManager companionDeviceManager,
+ ParcelFileDescriptor underlyingTransport,
+ SecureTransportListener secureTransportListener,
+ ParcelFileDescriptor remoteFd,
+ ParcelFileDescriptor localFd) {
+ mCompanionDeviceManager = companionDeviceManager;
+ mUnderlyingTransport = underlyingTransport;
+ mSecureTransportListener = secureTransportListener;
+ mRemoteFd = remoteFd;
+ mLocalIn = new AutoCloseInputStream(localFd);
+ mLocalOut = new AutoCloseOutputStream(localFd);
+ }
+
+ private void initialize() {
+ final long originalCallingIdentity = Binder.clearCallingIdentity();
+ try {
+ Slog.d(TAG, "Requesting CDM association.");
+ mCompanionDeviceManager.associate(
+ new AssociationRequest.Builder()
+ .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
+ .setSelfManaged(true)
+ .build(),
+ mLightWeightExecutor,
+ new CompanionDeviceManager.Callback() {
+ @Override
+ public void onAssociationCreated(AssociationInfo associationInfo) {
+ WearableSensingSecureChannel.this.onAssociationCreated(
+ associationInfo.getId());
+ }
+
+ @Override
+ public void onFailure(CharSequence error) {
+ Slog.e(
+ TAG,
+ "Failed to create CompanionDeviceManager association: "
+ + error);
+ onError();
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingIdentity);
+ }
+ }
+
+ private void onAssociationCreated(int associationId) {
+ Slog.i(TAG, "CDM association created.");
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ mAssociationId = associationId;
+ mCompanionDeviceManager.addOnMessageReceivedListener(
+ mMessageFromWearableExecutor,
+ CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+ mOnMessageReceivedListener);
+ mCompanionDeviceManager.addOnTransportsChangedListener(
+ mLightWeightExecutor, mOnTransportsChangedListener);
+ mCompanionDeviceManager.attachSystemDataTransport(
+ associationId,
+ new AutoCloseInputStream(mUnderlyingTransport),
+ new AutoCloseOutputStream(mUnderlyingTransport));
+ }
+ }
+
+ private void onTransportsChanged(List<AssociationInfo> associationInfos) {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ if (mAssociationId == null) {
+ Slog.e(TAG, "mAssociationId is null when transport changed");
+ return;
+ }
+ }
+ // Do not call onTransportAvailable() or onError() when holding the lock because it can
+ // cause a deadlock if the callback holds another lock.
+ boolean transportAvailable =
+ associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId);
+ if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) {
+ onTransportAvailable();
+ } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) {
+ Slog.i(TAG, "CDM transport is detached. This is not recoverable.");
+ onError();
+ }
+ }
+
+ private void onTransportAvailable() {
+ // Start sending data received from the remote stream to the wearable.
+ Slog.i(TAG, "Transport available");
+ mMessageToWearableExecutor.execute(
+ () -> {
+ int[] associationIdsToSendMessageTo = new int[] {mAssociationId};
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int readLen;
+ try {
+ while ((readLen = mLocalIn.read(buffer)) != -1) {
+ byte[] data = new byte[readLen];
+ System.arraycopy(buffer, 0, data, 0, readLen);
+ Slog.v(TAG, "Sending message to wearable");
+ mCompanionDeviceManager.sendMessage(
+ CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE,
+ data,
+ associationIdsToSendMessageTo);
+ }
+ } catch (IOException e) {
+ Slog.i(TAG, "IOException while reading from remote stream.");
+ onError();
+ return;
+ }
+ Slog.i(
+ TAG,
+ "Reached EOF when reading from remote stream. Reporting this as an"
+ + " error.");
+ onError();
+ });
+ mSecureTransportListener.onSecureTransportAvailable(mRemoteFd);
+ }
+
+ private void onMessageReceived(int associationIdForMessage, byte[] data) {
+ if (associationIdForMessage == mAssociationId) {
+ Slog.v(TAG, "Received message from wearable.");
+ try {
+ mLocalOut.write(data);
+ mLocalOut.flush();
+ } catch (IOException e) {
+ Slog.i(
+ TAG,
+ "IOException when writing to remote stream. Closing the secure channel.");
+ onError();
+ }
+ } else {
+ Slog.v(
+ TAG,
+ "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for"
+ + " another association. Ignoring the message.");
+ }
+ }
+
+ private void onError() {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ }
+ mSecureTransportListener.onError();
+ close();
+ }
+
+ /** Closes this secure channel and releases all resources. */
+ void close() {
+ synchronized (mLock) {
+ if (mClosed) {
+ return;
+ }
+ Slog.i(TAG, "Closing WearableSensingSecureChannel.");
+ mClosed = true;
+ if (mAssociationId != null) {
+ final long originalCallingIdentity = Binder.clearCallingIdentity();
+ try {
+ mCompanionDeviceManager.removeOnTransportsChangedListener(
+ mOnTransportsChangedListener);
+ mCompanionDeviceManager.removeOnMessageReceivedListener(
+ CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+ mOnMessageReceivedListener);
+ mCompanionDeviceManager.detachSystemDataTransport(mAssociationId);
+ mCompanionDeviceManager.disassociate(mAssociationId);
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingIdentity);
+ }
+ }
+ try {
+ mLocalIn.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Encountered IOException when closing local input stream.", ex);
+ }
+ try {
+ mLocalOut.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Encountered IOException when closing local output stream.", ex);
+ }
+ mMessageFromWearableExecutor.shutdown();
+ mMessageToWearableExecutor.shutdown();
+ mLightWeightExecutor.shutdown();
+ }
+ }
+
+ /**
+ * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a
+ * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown.
+ */
+ private static class SoftShutdownExecutor implements Executor {
+
+ private final ExecutorService mExecutorService;
+
+ SoftShutdownExecutor(ExecutorService executorService) {
+ mExecutorService = executorService;
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ try {
+ mExecutorService.execute(runnable);
+ } catch (RejectedExecutionException ex) {
+ Slog.d(TAG, "Received new runnable after shutdown. Ignoring.");
+ }
+ }
+
+ /** Shutdown the underlying ExecutorService. */
+ void shutdown() {
+ mExecutorService.shutdown();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 19ea9f9..ee865d3 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1610,7 +1610,7 @@
Slog.i(LOG_TAG, "computeChangedWindows()");
}
- final List<WindowInfo> windows = new ArrayList<>();
+ final List<WindowInfo> windows;
final List<AccessibilityWindow> visibleWindows = new ArrayList<>();
final int topFocusedDisplayId;
IBinder topFocusedWindowToken = null;
@@ -1640,69 +1640,11 @@
}
final Display display = dc.getDisplay();
display.getRealSize(mTempPoint);
- final int screenWidth = mTempPoint.x;
- final int screenHeight = mTempPoint.y;
-
- Region unaccountedSpace = mTempRegion;
- unaccountedSpace.set(0, 0, screenWidth, screenHeight);
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- Set<IBinder> addedWindows = mTempBinderSet;
- addedWindows.clear();
- boolean focusedWindowAdded = false;
-
- final int visibleWindowCount = visibleWindows.size();
-
- // Iterate until we figure out what is touchable for the entire screen.
- for (int i = 0; i < visibleWindowCount; i++) {
- final AccessibilityWindow a11yWindow = visibleWindows.get(i);
- final Region regionInWindow = new Region();
- a11yWindow.getTouchableRegionInWindow(regionInWindow);
- if (windowMattersToAccessibility(a11yWindow, regionInWindow,
- unaccountedSpace)) {
- addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
- if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
- updateUnaccountedSpace(a11yWindow, unaccountedSpace);
- }
- focusedWindowAdded |= a11yWindow.isFocused();
- } else if (a11yWindow.isUntouchableNavigationBar()) {
- // If this widow is navigation bar without touchable region, accounting the
- // region of navigation bar inset because all touch events from this region
- // would be received by launcher, i.e. this region is a un-touchable one
- // for the application.
- unaccountedSpace.op(
- getSystemBarInsetsFrame(
- mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
- unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- }
-
- if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
- break;
- }
- }
-
- // Remove child/parent references to windows that were not added.
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- WindowInfo window = windows.get(i);
- if (!addedWindows.contains(window.parentToken)) {
- window.parentToken = null;
- }
- if (window.childTokens != null) {
- final int childTokenCount = window.childTokens.size();
- for (int j = childTokenCount - 1; j >= 0; j--) {
- if (!addedWindows.contains(window.childTokens.get(j))) {
- window.childTokens.remove(j);
- }
- }
- // Leave the child token list if empty.
- }
- }
-
- addedWindows.clear();
+ windows = buildWindowInfoListLocked(visibleWindows, mTempPoint);
// Gets the top focused display Id and window token for supporting multi-display.
topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
@@ -1718,6 +1660,74 @@
mInitialized = true;
}
+ /**
+ * From a list of windows, decides windows to be exposed to accessibility based on touchable
+ * region in the screen.
+ */
+ private List<WindowInfo> buildWindowInfoListLocked(List<AccessibilityWindow> visibleWindows,
+ Point screenSize) {
+ final List<WindowInfo> windows = new ArrayList<>();
+ final Set<IBinder> addedWindows = mTempBinderSet;
+ addedWindows.clear();
+
+ boolean focusedWindowAdded = false;
+
+ final int visibleWindowCount = visibleWindows.size();
+
+ Region unaccountedSpace = mTempRegion;
+ unaccountedSpace.set(0, 0, screenSize.x, screenSize.y);
+
+ // Iterate until we figure out what is touchable for the entire screen.
+ for (int i = 0; i < visibleWindowCount; i++) {
+ final AccessibilityWindow a11yWindow = visibleWindows.get(i);
+ final Region regionInWindow = new Region();
+ a11yWindow.getTouchableRegionInWindow(regionInWindow);
+ if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) {
+ addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
+ if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
+ updateUnaccountedSpace(a11yWindow, unaccountedSpace);
+ }
+ focusedWindowAdded |= a11yWindow.isFocused();
+ } else if (a11yWindow.isUntouchableNavigationBar()) {
+ // If this widow is navigation bar without touchable region, accounting the
+ // region of navigation bar inset because all touch events from this region
+ // would be received by launcher, i.e. this region is a un-touchable one
+ // for the application.
+ unaccountedSpace.op(
+ getSystemBarInsetsFrame(
+ mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
+ unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+ }
+
+ if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
+ break;
+ }
+ }
+
+ // Remove child/parent references to windows that were not added.
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ WindowInfo window = windows.get(i);
+ if (!addedWindows.contains(window.parentToken)) {
+ window.parentToken = null;
+ }
+ if (window.childTokens != null) {
+ final int childTokenCount = window.childTokens.size();
+ for (int j = childTokenCount - 1; j >= 0; j--) {
+ if (!addedWindows.contains(window.childTokens.get(j))) {
+ window.childTokens.remove(j);
+ }
+ }
+ // Leave the child token list if empty.
+ }
+ }
+
+ addedWindows.clear();
+
+ return windows;
+ }
+
// Some windows should be excluded from unaccounted space computation, though they still
// should be reported
private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7b59759..c2117ea 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2441,11 +2441,11 @@
return false;
}
- if (mStartingData != null) {
+ if (hasStartingWindow()) {
return false;
}
- final WindowState mainWin = findMainWindow();
+ final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
if (mainWin != null && mainWin.mWinAnimator.getShown()) {
// App already has a visible window...why would you want a starting window?
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 85580ac..d99000e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -591,9 +591,18 @@
// Carefully collect grants without holding lock
if (activityInfo != null) {
- intentGrants = supervisor.mService.mUgmInternal.checkGrantUriPermissionFromIntent(
- intent, resolvedCallingUid, activityInfo.applicationInfo.packageName,
- UserHandle.getUserId(activityInfo.applicationInfo.uid));
+ if (android.security.Flags.contentUriPermissionApis()) {
+ intentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(activityInfo.applicationInfo.uid),
+ activityInfo.requireContentUriPermissionFromCaller);
+ } else {
+ intentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(activityInfo.applicationInfo.uid));
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0def5a1..8773366 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -276,6 +276,7 @@
import com.android.server.am.PendingIntentRecord;
import com.android.server.am.UserState;
import com.android.server.firewall.IntentFirewall;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
@@ -317,7 +318,6 @@
* {@hide}
*/
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
- private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -381,6 +381,7 @@
private PowerManagerInternal mPowerManagerInternal;
private UsageStatsManagerInternal mUsageStatsInternal;
+ GrammaticalInflectionManagerInternal mGrammaticalManagerInternal;
PendingIntentController mPendingIntentController;
IntentFirewall mIntentFirewall;
@@ -881,6 +882,8 @@
mActivityClientController.onSystemReady();
// TODO(b/258792202) Cleanup once ASM is ready to launch
ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor(), pm);
+ mGrammaticalManagerInternal = LocalServices.getService(
+ GrammaticalInflectionManagerInternal.class);
}
}
@@ -938,13 +941,8 @@
configuration.setLayoutDirection(configuration.locale);
}
- // Retrieve the grammatical gender from system property, set it into configuration which
- // will get updated later if the grammatical gender raw value of current configuration is
- // {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
- if (configuration.getGrammaticalGenderRaw() == Configuration.GRAMMATICAL_GENDER_UNDEFINED) {
- configuration.setGrammaticalGender(SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
- Configuration.GRAMMATICAL_GENDER_UNDEFINED));
- }
+ configuration.setGrammaticalGender(
+ mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration));
synchronized (mGlobalLock) {
mForceResizableActivities = forceResizable;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 25646f1..609ad1e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -83,6 +83,7 @@
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
import static java.lang.Integer.MAX_VALUE;
@@ -1444,6 +1445,7 @@
aInfo = info.first;
homeIntent = info.second;
}
+
if (aInfo == null || homeIntent == null) {
return false;
}
@@ -1452,6 +1454,11 @@
return false;
}
+ if (enableHomeDelay() && !mService.mAmInternal.getThemeOverlayReadiness()) {
+ Slog.d(TAG, "ThemeHomeDelay: Home launch was deferred.");
+ return false;
+ }
+
// Updates the home component of the intent.
homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 4ced5d5..f2dc55f 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -333,7 +333,9 @@
if (aInfo != null && overrideTaskTransition) {
final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission(
START_TASKS_FROM_RECENTS, callingPid, callingUid);
- if (startTasksFromRecentsPerm != PERMISSION_GRANTED) {
+ // Allow if calling uid is from assistant, or start task from recents
+ if (startTasksFromRecentsPerm != PERMISSION_GRANTED
+ && !isAssistant(supervisor.mService, callingUid)) {
final String msg = "Permission Denial: starting " + getIntentString(intent)
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ") with overrideTaskTransition=true";
diff --git a/services/core/java/com/android/server/wm/WindowList.java b/services/core/java/com/android/server/wm/WindowList.java
index dfeba40..1e888f5 100644
--- a/services/core/java/com/android/server/wm/WindowList.java
+++ b/services/core/java/com/android/server/wm/WindowList.java
@@ -24,7 +24,7 @@
*/
class WindowList<E> extends ArrayList<E> {
- void addFirst(E e) {
+ public void addFirst(E e) {
add(0, e);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 57448cb..366e1bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1114,8 +1114,11 @@
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean showBootMsgs, WindowManagerPolicy policy,
ActivityTaskManagerService atm) {
- return main(context, im, showBootMsgs, policy, atm, new DisplayWindowSettingsProvider(),
- SurfaceControl.Transaction::new, SurfaceControl.Builder::new);
+ final WindowManagerService wms = main(context, im, showBootMsgs, policy, atm,
+ new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new,
+ SurfaceControl.Builder::new);
+ WindowManagerGlobal.setWindowManagerServiceForSystemProcess(wms);
+ return wms;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6d2e8cc..6acf1f3 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -28,6 +28,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ProcessList.INVALID_ADJ;
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -298,6 +299,8 @@
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
+ private boolean mCanUseSystemGrammaticalGender;
+
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
@NonNull WindowProcessListener listener) {
@@ -319,6 +322,9 @@
mIsActivityConfigOverrideAllowed = false;
}
+ mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
+ && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
+ mInfo.packageName);
onConfigurationChanged(atm.getGlobalConfiguration());
mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
}
@@ -1568,6 +1574,11 @@
return;
}
+ if (mCanUseSystemGrammaticalGender) {
+ config.setGrammaticalGender(
+ mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId));
+ }
+
if (mPauseConfigurationDispatchCount > 0) {
mHasPendingConfigurationChange = true;
return;
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index b1349ea..f5ba50d 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -27,6 +27,7 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -149,7 +150,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation) {
+ public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
// Not needed since UI is not involved
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index b6f7eb3..be4b9e1 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -31,6 +31,7 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -163,7 +164,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation) {
+ public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
String exception = CreateCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 84b5cb7..534c842 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -15,6 +15,8 @@
*/
package com.android.server.credentials;
+import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
+
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -34,7 +36,6 @@
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Slog;
import java.util.ArrayList;
import java.util.HashSet;
@@ -79,20 +80,25 @@
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
- mCallbacks.onUiSelection(selection);
- } else {
- Slog.i(TAG, "No selection found in UI result");
+ ResultReceiver resultReceiver = resultData.getParcelable(
+ EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver.class);
+ mCallbacks.onUiSelection(selection, resultReceiver);
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ true,
+ resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver.class));
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ false,
+ resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver.class));
break;
case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
mStatus = UiStatus.TERMINATED;
@@ -116,10 +122,10 @@
*/
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
- void onUiSelection(UserSelectionDialogResult selection);
+ void onUiSelection(UserSelectionDialogResult selection, ResultReceiver resultReceiver);
/** Called when the UI is canceled without a successful provider result. */
- void onUiCancellation(boolean isUserCancellation);
+ void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver);
/** Called when the selector UI fails to come up (mostly due to parsing issue today). */
void onUiSelectorInvocationFailure();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 9e362b3..adb1b72 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.Constants;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
@@ -29,10 +30,13 @@
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
+import android.service.credentials.CredentialProviderService;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -153,7 +157,8 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation) {
+ public void onUiCancellation(boolean isUserCancellation,
+ @Nullable ResultReceiver finalResponseReceiver) {
String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
@@ -161,7 +166,12 @@
message = "The UI was interrupted - please try again.";
}
mRequestSessionMetric.collectFrameworkException(exception);
- respondToClientWithErrorAndFinish(exception, message);
+ if (finalResponseReceiver != null) {
+ Bundle resultData = new Bundle();
+ finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
+ } else {
+ respondToClientWithErrorAndFinish(exception, message);
+ }
}
@Override
@@ -197,7 +207,16 @@
public void onFinalResponseReceived(ComponentName componentName,
GetCredentialResponse response) {
Slog.d(TAG, "onFinalResponseReceived");
- respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
+ if (this.mFinalResponseReceiver != null) {
+ Slog.d(TAG, "onFinalResponseReceived sending through final receiver");
+ Bundle resultData = new Bundle();
+ resultData.putParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+ mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
+ finishSession(/*propagateCancellation=*/ false);
+ } else {
+ Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
+ }
}
/**
@@ -212,6 +231,5 @@
*/
public int getAutofillRequestId() {
return mAutofillRequestId;
-
}
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 4068d7b..a279337 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -31,6 +31,7 @@
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -165,7 +166,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation) {
+ public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
String exception = GetCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index bf7df86..633c9c4 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -17,6 +17,7 @@
package com.android.server.credentials;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -33,6 +34,7 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -101,6 +103,9 @@
protected PendingIntent mPendingIntent;
+ @Nullable
+ protected ResultReceiver mFinalResponseReceiver;
+
@NonNull
protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
@@ -219,7 +224,8 @@
// UI callbacks
@Override // from CredentialManagerUiCallbacks
- public void onUiSelection(UserSelectionDialogResult selection) {
+ public void onUiSelection(UserSelectionDialogResult selection,
+ ResultReceiver finalResponseReceiver) {
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
Slog.w(TAG, "Request has already been completed. This is strange.");
return;
@@ -234,6 +240,7 @@
Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
return;
}
+ mFinalResponseReceiver = finalResponseReceiver;
ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric;
int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric()
.size();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 74d544f..f87fd8d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -34,6 +34,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY;
@@ -110,6 +111,8 @@
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -220,6 +223,7 @@
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
@@ -6012,10 +6016,10 @@
// Make sure the caller has any active admin with the right policy or
// the required permission.
if (isUnicornFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(
+ admin = enforcePermissionsAndGetEnforcingAdmin(
/* admin= */ null,
- /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
- USES_POLICY_FORCE_LOCK,
+ /* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE},
+ /* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK,
caller.getPackageName(),
getAffectedUser(parent)
).getActiveAdmin();
@@ -17926,6 +17930,13 @@
|| isProfileOwner(caller) || isFinancedDeviceOwner(caller));
toggleBackupServiceActive(caller.getUserId(), enabled);
+
+ if (backupServiceSecurityLogEventEnabled()) {
+ if (SecurityLog.isLoggingEnabled()) {
+ SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
+ caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
+ }
+ }
}
@Override
@@ -23165,6 +23176,90 @@
}
}
+ private EnforcingAdmin enforceCanCallContentProtectionLocked(
+ ComponentName who, String callerPackageName) {
+ CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+ final int userId = caller.getUserId();
+
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ who,
+ MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+ caller.getPackageName(),
+ userId
+ );
+ if ((isDeviceOwner(caller) || isProfileOwner(caller))
+ && !canDPCManagedUserUseLockTaskLocked(userId)) {
+ throw new SecurityException(
+ "User " + userId + " is not allowed to use content protection");
+ }
+ return enforcingAdmin;
+ }
+
+ private void enforceCanQueryContentProtectionLocked(
+ ComponentName who, String callerPackageName) {
+ CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+ final int userId = caller.getUserId();
+
+ enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, caller.getPackageName(), userId);
+ if ((isDeviceOwner(caller) || isProfileOwner(caller))
+ && !canDPCManagedUserUseLockTaskLocked(userId)) {
+ throw new SecurityException(
+ "User " + userId + " is not allowed to use content protection");
+ }
+ }
+
+ @Override
+ public void setContentProtectionPolicy(
+ ComponentName who, String callerPackageName, @ContentProtectionPolicy int policy)
+ throws SecurityException {
+ if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+ return;
+ }
+
+ CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CONTENT_PROTECTION_POLICY);
+
+ EnforcingAdmin enforcingAdmin;
+ synchronized (getLockObject()) {
+ enforcingAdmin = enforceCanCallContentProtectionLocked(who, caller.getPackageName());
+ }
+
+ if (policy == CONTENT_PROTECTION_DISABLED) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.CONTENT_PROTECTION,
+ enforcingAdmin,
+ caller.getUserId());
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.CONTENT_PROTECTION,
+ enforcingAdmin,
+ new IntegerPolicyValue(policy),
+ caller.getUserId());
+ }
+ }
+
+ @Override
+ public @ContentProtectionPolicy int getContentProtectionPolicy(
+ ComponentName who, String callerPackageName) {
+ if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+ return CONTENT_PROTECTION_DISABLED;
+ }
+
+ CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+ final int userHandle = caller.getUserId();
+
+ synchronized (getLockObject()) {
+ enforceCanQueryContentProtectionLocked(who, caller.getPackageName());
+ }
+ Integer policy = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.CONTENT_PROTECTION, userHandle);
+ if (policy == null) {
+ return CONTENT_PROTECTION_DISABLED;
+ } else {
+ return policy;
+ }
+ }
+
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 0fc8c5e..27f1834 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -341,6 +341,13 @@
PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context),
new BooleanPolicySerializer());
+ static PolicyDefinition<Integer> CONTENT_PROTECTION = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY),
+ new MostRecent<>(),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ new IntegerPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
@@ -374,6 +381,8 @@
PERSONAL_APPS_SUSPENDED);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY,
USB_DATA_SIGNALING);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
+ CONTENT_PROTECTION);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b79d20a..c55d709 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,6 +48,7 @@
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.credentials.CredentialManager;
+import android.credentials.flags.Flags;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.graphics.GraphicsStatsService;
@@ -2795,9 +2796,14 @@
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
if (credentialManagerEnabled) {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ if(isWatch &&
+ !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+ Slog.d(TAG, "CredentialManager disabled on wear.");
+ } else {
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ }
} else {
Slog.d(TAG, "CredentialManager disabled.");
}
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
new file mode 100644
index 0000000..4b578af
--- /dev/null
+++ b/services/java/com/android/server/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.server"
+
+flag {
+ namespace: "system_performance"
+ name: "telemetry_apis_service"
+ description: "Control service portion of telemetry APIs feature."
+ is_fixed_read_only: true
+ bug: "324153471"
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index d479e52..682ed91 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -32,6 +32,7 @@
":BackgroundInstallControlServiceTestApp",
":BackgroundInstallControlMockApp1",
":BackgroundInstallControlMockApp2",
+ ":BackgroundInstallControlMockApp3",
],
test_suites: [
"general-tests",
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
index 1e7a78a..a352851 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
@@ -34,6 +34,9 @@
<option name="push-file"
key="BackgroundInstallControlMockApp2.apk"
value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" />
+ <option name="push-file"
+ key="BackgroundInstallControlMockApp3.apk"
+ value="/data/local/tmp/BackgroundInstallControlMockApp3.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
index c99e712..5092a46 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -68,6 +68,20 @@
assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull();
}
+ @Test
+ public void testRegisterCallback() throws Exception {
+ runDeviceTest(
+ "BackgroundInstallControlServiceTest",
+ "testRegisterBackgroundInstallControlCallback");
+ }
+
+ @Test
+ public void testUnregisterCallback() throws Exception {
+ runDeviceTest(
+ "BackgroundInstallControlServiceTest",
+ "testUnregisterBackgroundInstallControlCallback");
+ }
+
private void installPackage(String path) throws DeviceNotAvailableException {
String cmd = "pm install -t --force-queryable " + path;
CommandResult result = getDevice().executeShellV2Command(cmd);
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
index b23f591..ac041f4 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -16,38 +16,59 @@
package com.android.server.pm.test.app;
-import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
-
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileInputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class BackgroundInstallControlServiceTest {
private static final String TAG = "BackgroundInstallControlServiceTest";
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.server.pm.test.app.BackgroundInstallControlServiceTest"
+ + ".ACTION_INSTALL_COMMIT";
private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";
+ private static final String TEST_DATA_DIR = "/data/local/tmp/";
+
+ private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk";
private IBackgroundInstallControlService mIBics;
@Before
@@ -74,10 +95,9 @@
PackageManager.MATCH_ALL, Process.myUserHandle()
.getIdentifier());
} catch (RemoteException e) {
- throw new RuntimeException(e);
+ throw e.rethrowFromSystemServer();
}
- },
- GET_BACKGROUND_INSTALLED_PACKAGES);
+ });
assertThat(slice).isNotNull();
var packageList = slice.getList();
@@ -94,4 +114,150 @@
.collect(Collectors.toSet());
assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
}
+
+ @Test
+ public void testRegisterBackgroundInstallControlCallback()
+ throws Exception {
+ String testPackageName = "test";
+ int testUserId = 1;
+ ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>();
+ IRemoteCallback testCallback =
+ new IRemoteCallback.Stub() {
+ private final ArrayList<Pair<String, Integer>> mArray = sharedResource;
+
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ mArray.add(new Pair(testPackageName, testUserId));
+ }
+ };
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mIBics,
+ (bics) -> {
+ try {
+ bics.registerBackgroundInstallCallback(testCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
+
+ assertUntil(() -> sharedResource.size() == 1, 2000);
+ assertThat(sharedResource.get(0).first).isEqualTo(testPackageName);
+ assertThat(sharedResource.get(0).second).isEqualTo(testUserId);
+ }
+
+ @Test
+ public void testUnregisterBackgroundInstallControlCallback() {
+ String testValue = "test";
+ ArrayList<String> sharedResource = new ArrayList<>();
+ IRemoteCallback testCallback =
+ new IRemoteCallback.Stub() {
+ private final ArrayList<String> mArray = sharedResource;
+
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ mArray.add(testValue);
+ }
+ };
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mIBics,
+ (bics) -> {
+ try {
+ bics.registerBackgroundInstallCallback(testCallback);
+ bics.unregisterBackgroundInstallCallback(testCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
+
+ assertUntil(sharedResource::isEmpty, 2000);
+ }
+
+ private static boolean installPackage(String apkPath, String packageName) {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final CountDownLatch installLatch = new CountDownLatch(1);
+ final BroadcastReceiver installReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ int packageInstallStatus =
+ intent.getIntExtra(
+ PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE_INVALID);
+ if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) {
+ installLatch.countDown();
+ }
+ }
+ };
+ final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT);
+ context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams params =
+ new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED);
+ try {
+ int sessionId = packageInstaller.createSession(params);
+ PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+ OutputStream out = session.openWrite(packageName, 0, -1);
+ FileInputStream fis = new FileInputStream(apkPath);
+ byte[] buffer = new byte[65536];
+ int size;
+ while ((size = fis.read(buffer)) != -1) {
+ out.write(buffer, 0, size);
+ }
+ session.fsync(out);
+ fis.close();
+ out.close();
+
+ runWithShellPermissionIdentity(
+ () -> {
+ session.commit(createPendingIntent(context).getIntentSender());
+ installLatch.await(5, TimeUnit.SECONDS);
+ });
+ return true;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static PendingIntent createPendingIntent(Context context) {
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(
+ context,
+ 1,
+ new Intent(ACTION_INSTALL_COMMIT)
+ .setPackage(
+ BackgroundInstallControlServiceTest.class.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
+ return pendingIntent;
+ }
+
+ private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command)
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+ try {
+ command.run();
+ } finally {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
+ private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) {
+ long endTime = System.currentTimeMillis() + timeoutMs;
+ while (System.currentTimeMillis() <= endTime) {
+ if (condition.get()) return;
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ assertThat(condition.get()).isTrue();
+ }
}
\ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
index 7804f4c..39b0ff7 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
@@ -50,3 +50,11 @@
"--rename-manifest-package com.android.servicestests.apps.bicmockapp2",
],
}
+
+android_test_helper_app {
+ name: "BackgroundInstallControlMockApp3",
+ defaults: ["bic-mock-app-defaults"],
+ aaptflags: [
+ "--rename-manifest-package com.android.servicestests.apps.bicmockapp3",
+ ],
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 2c8b1cd..349b831 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -54,7 +54,8 @@
ParsedActivity::getTheme,
ParsedActivity::getUiOptions,
ParsedActivity::isSupportsSizeChanges,
- ParsedActivity::getRequiredDisplayCategory
+ ParsedActivity::getRequiredDisplayCategory,
+ ParsedActivity::getRequireContentUriPermissionFromCaller
)
override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d307608..b374af6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -149,8 +149,8 @@
callingUidInt.set(it.callingUid)
callingUserIdInt.set(it.callingUserId)
service.proxy = it.proxy
- service.addPackage(visiblePkgState)
- service.addPackage(invisiblePkgState)
+ service.addPackage(visiblePkgState, null)
+ service.addPackage(invisiblePkgState, null)
service.block(it)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 5edf30a3..a8100af 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -566,7 +566,7 @@
}
private fun DomainVerificationService.addPackages(vararg pkgStates: PackageStateInternal) =
- pkgStates.forEach(::addPackage)
+ pkgStates.forEach {pkg: PackageStateInternal -> addPackage(pkg, null)}
private fun makeManager(service: DomainVerificationService, userId: Int) =
DomainVerificationManager(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 85f0125..e0407c1 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -21,6 +21,7 @@
import android.content.pm.Signature
import android.content.pm.SigningDetails
import android.content.pm.verify.domain.DomainOwner
+import android.content.pm.verify.domain.DomainSet
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
@@ -48,16 +49,16 @@
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.security.PublicKey
-import java.util.UUID
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.doReturn
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.security.PublicKey
+import java.util.UUID
class DomainVerificationPackageTest {
@@ -88,7 +89,7 @@
@Test
fun addPackageFirstTime() {
val service = makeService(pkg1, pkg2)
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
val info = service.getInfo(pkg1.packageName)
assertThat(info.packageName).isEqualTo(pkg1.packageName)
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -120,8 +121,8 @@
systemConfiguredPackageNames = ArraySet(setOf(pkg1.packageName, pkg2.packageName)),
pkg1, pkg2
)
- service.addPackage(pkg1)
- service.addPackage(pkg2)
+ service.addPackage(pkg1, null)
+ service.addPackage(pkg2, null)
service.getInfo(pkg1.packageName).apply {
assertThat(packageName).isEqualTo(pkg1.packageName)
@@ -198,7 +199,7 @@
val service = makeService(pkg1, pkg2)
val computer = mockComputer(pkg1, pkg2)
service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
val info = service.getInfo(pkg1.packageName)
assertThat(info.packageName).isEqualTo(pkg1.packageName)
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -248,7 +249,7 @@
val service = makeService(pkg1, pkg2)
val computer = mockComputer(pkg1, pkg2)
service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
val info = service.getInfo(pkg1.packageName)
assertThat(info.packageName).isEqualTo(pkg1.packageName)
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -307,7 +308,7 @@
service.readSettings(computer, Xml.resolvePullParser(it))
}
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
assertAddPackageActivePendingRestoredState(service)
}
@@ -321,7 +322,7 @@
service.readSettings(computer, Xml.resolvePullParser(it))
}
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
val userState = service.getUserState(pkg1.packageName)
assertThat(userState.packageName).isEqualTo(pkg1.packageName)
@@ -345,11 +346,55 @@
service.restoreSettings(computer, Xml.resolvePullParser(it))
}
- service.addPackage(pkg1)
+ service.addPackage(pkg1, null)
assertAddPackageActivePendingRestoredState(service, expectRestore = true)
}
+ @Test
+ fun addPackageWithPreVerifiedDomains() {
+ val service = makeService(pkg1)
+ val pkg1 = mockPkgState(
+ PKG_ONE,
+ UUID_ONE,
+ SIGNATURE_ONE,
+ autoVerifyDomains = listOf(DOMAIN_1, DOMAIN_2),
+ otherDomains = listOf(DOMAIN_3, DOMAIN_4))
+ service.addPackage(pkg1, DomainSet(setOf(DOMAIN_1, DOMAIN_3)))
+ val info = service.getInfo(pkg1.packageName)
+ assertThat(info.packageName).isEqualTo(pkg1.packageName)
+ assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
+ // Test that DOMAIN_1 is pre-verified and DOMAIN_3 is ignored because autoVerify=false
+ assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+
+ val userState = service.getUserState(pkg1.packageName)
+ assertThat(userState.packageName).isEqualTo(pkg1.packageName)
+ assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
+ assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
+ assertThat(userState.user.identifier).isEqualTo(USER_ID)
+ assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ ))
+
+ assertThat(service.queryValidVerificationPackageNames())
+ .containsExactly(pkg1.packageName)
+
+ // Test that the pre-verified state can be overwritten to be disapproved
+ service.setDomainVerificationStatusInternal(
+ PKG_ONE,
+ DomainVerificationState.STATE_DENIED,
+ ArraySet(setOf(DOMAIN_1, DOMAIN_2)))
+ val infoUpdated = service.getInfo(pkg1.packageName)
+ assertThat(infoUpdated.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_UNMODIFIABLE,
+ DOMAIN_2 to STATE_UNMODIFIABLE,
+ ))
+ }
+
/**
* Shared string that contains invalid [DOMAIN_3] and [DOMAIN_4] which should be stripped from
* the final state.
@@ -447,7 +492,7 @@
val map = mutableMapOf<String, PackageStateInternal>()
val service = makeService { map[it] }
- service.addPackage(pkgBefore)
+ service.addPackage(pkgBefore, null)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
@@ -482,7 +527,7 @@
map[pkgName] = pkgAfter
- service.migrateState(pkgBefore, pkgAfter)
+ service.migrateState(pkgBefore, pkgAfter, null)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
@@ -503,7 +548,7 @@
val map = mutableMapOf<String, PackageStateInternal>()
val service = makeService { map[it] }
- service.addPackage(pkgBefore)
+ service.addPackage(pkgBefore, null)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
@@ -522,7 +567,7 @@
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
- service.migrateState(pkgBefore, pkgAfter)
+ service.migrateState(pkgBefore, pkgAfter, null)
map[pkgName] = pkgAfter
@@ -550,7 +595,7 @@
val map = mutableMapOf<String, PackageStateInternal>()
val service = makeService { map[it] }
- service.addPackage(pkgBefore)
+ service.addPackage(pkgBefore, null)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
@@ -571,7 +616,7 @@
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
- service.migrateState(pkgBefore, pkgAfter)
+ service.migrateState(pkgBefore, pkgAfter, null)
map[pkgName] = pkgAfter
@@ -596,7 +641,7 @@
val map = mutableMapOf<String, PackageStateInternal>()
val service = makeService { map[it] }
- service.addPackage(pkgBefore)
+ service.addPackage(pkgBefore, null)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
@@ -615,7 +660,7 @@
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
- service.migrateState(pkgBefore, pkgAfter)
+ service.migrateState(pkgBefore, pkgAfter, null)
map[pkgName] = pkgAfter
@@ -640,7 +685,7 @@
val map = mutableMapOf<String, PackageStateInternal>()
val service = makeService { map[it] }
- service.addPackage(pkgBefore)
+ service.addPackage(pkgBefore, null)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
@@ -667,7 +712,7 @@
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
- service.migrateState(pkgBefore, pkgAfter)
+ service.migrateState(pkgBefore, pkgAfter, null)
map[pkgName] = pkgAfter
@@ -685,6 +730,30 @@
}
@Test
+ fun migratePackageWithPreVerifiedDomains() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgState(pkgName, UUID_ONE, SIGNATURE_ONE, emptyList())
+ val pkgAfter = mockPkgState(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
+
+ val map = mutableMapOf<String, PackageStateInternal>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore, null)
+ service.migrateState(pkgBefore, pkgAfter, DomainSet(setOf(DOMAIN_1, DOMAIN_3)))
+
+ map[pkgName] = pkgAfter
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+ }
+
+ @Test
fun backupAndRestore() {
// This test acts as a proxy for true user restore through PackageManager,
// as that's much harder to test for real.
@@ -694,8 +763,8 @@
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
val serviceBefore = makeService(pkg1, pkg2)
val computerBefore = mockComputer(pkg1, pkg2)
- serviceBefore.addPackage(pkg1)
- serviceBefore.addPackage(pkg2)
+ serviceBefore.addPackage(pkg1, null)
+ serviceBefore.addPackage(pkg2, null)
serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS)
serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.packageName, false, 10)
@@ -748,8 +817,8 @@
val serviceAfter = makeService(pkg1, pkg2)
val computerAfter = mockComputer(pkg1, pkg2)
- serviceAfter.addPackage(pkg1)
- serviceAfter.addPackage(pkg2)
+ serviceAfter.addPackage(pkg1, null)
+ serviceAfter.addPackage(pkg2, null)
// Check the state is default before the restoration applies
listOf(0, 10).forEach {
@@ -858,8 +927,8 @@
)
val service = makeService(pkg1, pkg2)
- service.addPackage(pkg1)
- service.addPackage(pkg2)
+ service.addPackage(pkg1, null)
+ service.addPackage(pkg2, null)
// Approve domain 1, 3, and 4 for package 2 for both users
USER_IDS.forEach {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index a5c4f6c..9748307 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -106,7 +106,7 @@
fun service(name: String, block: DomainVerificationService.() -> Unit) =
Params(makeService, name) { service ->
service.proxy = proxy
- service.addPackage(mockPkgState())
+ service.addPackage(mockPkgState(), null)
service.block()
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index ae570a3..56ab841 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -97,8 +97,8 @@
}
}
})
- addPackage(pkg1)
- addPackage(pkg2)
+ addPackage(pkg1, null)
+ addPackage(pkg2, null)
// Starting state for all tests is to have domain 1 enabled for the first package
setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID)
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index d928306..9f97551 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
"testng",
"compatibility-device-util-axt",
"flag-junit",
+ "am_flags_lib",
],
libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index f875f65..fb47aa8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -38,6 +38,8 @@
import android.os.HandlerThread;
import android.os.TestLooperManager;
import android.os.UserHandle;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.SparseArray;
@@ -93,6 +95,9 @@
.spyStatic(ProcessList.class)
.build();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
@Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 75409d9..3f6117b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -75,6 +75,8 @@
import android.os.PowerExemptionManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.proto.ProtoOutputStream;
@@ -135,6 +137,17 @@
ProcessStartBehavior.SUCCESS);
/**
+ * Map of processes to behaviors indicating how the new processes should behave as needed
+ * by the tests.
+ */
+ private ArrayMap<String, ProcessBehavior> mNewProcessBehaviors = new ArrayMap<>();
+
+ /**
+ * Map of processes to behaviors indicating how the new process starts should result in.
+ */
+ private ArrayMap<String, ProcessStartBehavior> mNewProcessStartBehaviors = new ArrayMap<>();
+
+ /**
* Collection of all active processes during current test run.
*/
private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
@@ -161,15 +174,17 @@
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
assertHealth();
- final ProcessStartBehavior behavior = mNextProcessStartBehavior
- .getAndSet(ProcessStartBehavior.SUCCESS);
+ final String processName = invocation.getArgument(0);
+ final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault(
+ processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS));
if (behavior == ProcessStartBehavior.FAIL_NULL) {
return null;
}
- final String processName = invocation.getArgument(0);
final ApplicationInfo ai = invocation.getArgument(1);
+ final ProcessBehavior processBehavior = mNewProcessBehaviors.getOrDefault(
+ processName, ProcessBehavior.NORMAL);
final ProcessRecord res = makeActiveProcessRecord(ai, processName,
- ProcessBehavior.NORMAL, UnaryOperator.identity());
+ processBehavior, UnaryOperator.identity());
final ProcessRecord deliverRes;
switch (behavior) {
case SUCCESS_PREDECESSOR:
@@ -274,6 +289,8 @@
assertEquals(app.toShortString(), ProcessList.SCHED_GROUP_UNDEFINED,
mQueue.getPreferredSchedulingGroupLocked(app));
}
+ mNewProcessBehaviors.clear();
+ mNewProcessStartBehaviors.clear();
}
@Override
@@ -955,6 +972,40 @@
}
/**
+ * Verify that we handle manifest receivers in a process that always
+ * responds with {@link DeadObjectException} even after restarting.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+ public void testRepeatedDead_Manifest() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ mNewProcessBehaviors.put(PACKAGE_GREEN, ProcessBehavior.DEAD);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0))));
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ waitForIdle();
+
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // Modern queue always kills the target process when broadcast delivery fails, where as
+ // the legacy queue leaves the process killing task to AMS
+ if (mImpl == Impl.MODERN) {
+ assertNull(receiverGreenApp);
+ }
+ final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyScheduleReceiver(receiverBlueApp, airplane);
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ verifyScheduleReceiver(receiverYellowApp, timezone);
+ }
+
+ /**
* Verify that we handle the system failing to start a process.
*/
@Test
@@ -1142,6 +1193,49 @@
}
/**
+ * Verify that when BroadcastQueue doesn't get notified when a process gets killed repeatedly,
+ * it doesn't get stuck.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+ public void testRepeatedKillWithoutNotify() throws Exception {
+ // Legacy queue does not handle repeated kills that don't get notified.
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+ mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+ withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+ waitForIdle();
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE));
+
+ // Modern queue always kills the target process when broadcast delivery fails, where as
+ // the legacy queue leaves the process killing task to AMS
+ if (mImpl == Impl.MODERN) {
+ assertNull(receiverGreenApp);
+ }
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+ verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+ }
+
+ /**
* Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing
* process due to pending sync binder transactions, is delivered as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 3355a6c..fab7610 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -44,6 +44,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -65,7 +67,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
+import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
+import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -89,6 +93,8 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -107,6 +113,8 @@
@Mock
private ActivityManagerInternal mActivityMangerInternal;
@Mock
+ private BatteryManagerInternal mBatteryManagerInternal;
+ @Mock
private Context mContext;
@Mock
private PackageManagerInternal mPackageManagerInternal;
@@ -114,6 +122,8 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -137,7 +147,7 @@
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(AppStandbyInternal.class))
.when(() -> LocalServices.getService(AppStandbyInternal.class));
- doReturn(mock(BatteryManagerInternal.class))
+ doReturn(mBatteryManagerInternal)
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -186,8 +196,17 @@
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
+ setChargingPolicy(Integer.MIN_VALUE);
+
+ ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
+ ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
+
mService = new TestJobSchedulerService(mContext);
mService.waitOnAsyncLoadingForTesting();
+
+ verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
+ chargingPolicyChangeListenerCaptor.capture());
+ mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
}
@After
@@ -1718,6 +1737,127 @@
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
}
+ @Test
+ public void testBatteryStateTrackerRegistersForImportantIntents() {
+ verify(mContext).registerReceiver(any(), ArgumentMatchers.argThat(filter -> true
+ && filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)
+ && filter.hasAction(Intent.ACTION_BATTERY_LEVEL_CHANGED)
+ && filter.hasAction(Intent.ACTION_BATTERY_LOW)
+ && filter.hasAction(Intent.ACTION_BATTERY_OKAY)
+ && filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+ && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+ }
+
+ @Test
+ public void testIsCharging_standardChargingIntent() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ Intent chargingIntent = new Intent(BatteryManager.ACTION_CHARGING);
+ Intent dischargingIntent = new Intent(BatteryManager.ACTION_DISCHARGING);
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, chargingIntent);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_batteryTooLow() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+
+ setBatteryLevel(70);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_chargeBelowThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(5);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 5; level < 80; ++level) {
+ setBatteryLevel(level);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_dischargeAboveThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(80);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 80; level > 60; --level) {
+ setBatteryLevel(level);
+ assertEquals(level >= 70, tracker.isCharging());
+ assertEquals(level >= 70, mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_notPluggedIn() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_DISCONNECTED));
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(50);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(70);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(95);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(100);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
/** Tests that rare job batching works as expected. */
@Test
public void testConnectivityJobBatching() {
@@ -2257,4 +2397,17 @@
assertFalse(mService.getPendingJobQueue().contains(job2b));
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
}
+
+ private void setBatteryLevel(int level) {
+ doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
+ mService.mBatteryStateTracker
+ .onReceive(mContext, new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED));
+ }
+
+ private void setChargingPolicy(int policy) {
+ doReturn(policy).when(mBatteryManagerInternal).getChargingPolicy();
+ if (mChargingPolicyChangeListener != null) {
+ mChargingPolicyChangeListener.onChargingPolicyChanged(policy);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index d4ef647..ea937de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -25,14 +25,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.BatteryManagerInternal;
@@ -49,8 +46,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -63,7 +58,6 @@
private BatteryController mBatteryController;
private FlexibilityController mFlexibilityController;
- private BroadcastReceiver mPowerReceiver;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid;
@@ -99,10 +93,6 @@
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Initialize real objects.
- // Capture the listeners.
- ArgumentCaptor<BroadcastReceiver> receiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
-
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
@@ -111,11 +101,6 @@
mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
mBatteryController.startTrackingLocked();
- verify(mContext).registerReceiver(receiverCaptor.capture(),
- ArgumentMatchers.argThat(filter ->
- filter.hasAction(Intent.ACTION_POWER_CONNECTED)
- && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
- mPowerReceiver = receiverCaptor.getValue();
try {
mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
// Need to do this since we're using a mock JS and not a real object.
@@ -159,9 +144,11 @@
}
private void setPowerConnected(boolean connected) {
- Intent intent = new Intent(
- connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
- mPowerReceiver.onReceive(mContext, intent);
+ doReturn(connected).when(mJobSchedulerService).isPowerConnected();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
}
private void setUidBias(int uid, int bias) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
new file mode 100644
index 0000000..574f369
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.pm;
+
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY;
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/** Unit tests for {@link BackgroundInstallControlCallbackHelper} */
+@Presubmit
+@RunWith(JUnit4.class)
+public class BackgroundInstallControlCallbackHelperTest {
+
+ private final IRemoteCallback mCallback =
+ spy(
+ new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle extras) {}
+ });
+
+ private BackgroundInstallControlCallbackHelper mCallbackHelper;
+
+ @Before
+ public void setup() {
+ mCallbackHelper = new BackgroundInstallControlCallbackHelper();
+ }
+
+ @Test
+ public void registerBackgroundInstallControlCallback_registers_successfully() {
+ mCallbackHelper.registerBackgroundInstallCallback(mCallback);
+
+ synchronized (mCallbackHelper.mCallbacks) {
+ assertEquals(1, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
+ assertEquals(mCallback, mCallbackHelper.mCallbacks.getRegisteredCallbackItem(0));
+ }
+ }
+
+ @Test
+ public void unregisterBackgroundInstallControlCallback_unregisters_successfully() {
+ synchronized (mCallbackHelper.mCallbacks) {
+ mCallbackHelper.mCallbacks.register(mCallback);
+ }
+
+ mCallbackHelper.unregisterBackgroundInstallCallback(mCallback);
+
+ synchronized (mCallbackHelper.mCallbacks) {
+ assertEquals(0, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
+ }
+ }
+
+ @Test
+ public void notifyAllCallbacks_broadcastsToCallbacks()
+ throws RemoteException {
+ String testPackageName = "testname";
+ int testUserId = 1;
+ mCallbackHelper.registerBackgroundInstallCallback(mCallback);
+
+ mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName);
+
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture());
+ Bundle receivedBundle = bundleCaptor.getValue();
+ assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY));
+ assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY));
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ad6e2c6..3743483 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -154,6 +154,7 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"services.core",
+ "flag-junit",
],
srcs: [
"src/com/android/server/uri/**/*.java",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 2dfabd0..b12d6da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -893,13 +893,13 @@
@Test
public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
- final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
assertEquals(token, mMockHostToken);
}
@Test
public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
- final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
assertNull(token);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 9cdaec6..7a77392 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -472,6 +472,7 @@
verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
verify(mSideFpsController).hide(anyInt());
+ verify(mHal, times(2)).setIgnoreDisplayTouches(false);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 951c9393..3ee54f5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -327,6 +328,7 @@
verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
verify(mSideFpsController).hide(anyInt());
+ verify(mHal, times(2)).setIgnoreDisplayTouches(false);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 5943832..07e6ab2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,10 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
@@ -86,9 +85,8 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.uniqueId = "uniqueId";
- doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ setUpDisplay(1 /* displayId */);
+ setUpDisplay(2 /* displayId */);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
@@ -100,6 +98,16 @@
threadVerifier);
}
+ void setUpDisplay(int displayId) {
+ final String uniqueId = "uniqueId:" + displayId;
+ doAnswer((inv) -> {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = uniqueId;
+ return displayInfo;
+ }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+ mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
+ }
+
@After
public void tearDown() {
mInputManagerMockHelper.tearDown();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 3722247..74e854e4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -20,7 +20,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import android.hardware.input.IInputDevicesChangedListener;
@@ -28,12 +27,15 @@
import android.hardware.input.InputManagerGlobal;
import android.os.RemoteException;
import android.testing.TestableLooper;
+import android.view.Display;
import android.view.InputDevice;
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.stream.IntStream;
@@ -49,6 +51,10 @@
private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
private final List<InputDevice> mDevices = new ArrayList<>();
private IInputDevicesChangedListener mDevicesChangedListener;
+ private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
+ new HashMap<>();
+ private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+ new HashMap<>();
InputManagerMockHelper(TestableLooper testableLooper,
InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
@@ -73,8 +79,10 @@
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
doAnswer(inv -> mDevices.get(inv.getArgument(0)))
.when(mIInputManagerMock).getInputDevice(anyInt());
- doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
- doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+ doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), inv.getArgument(1))).when(
+ mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
+ mIInputManagerMock).removeUniqueIdAssociation(anyString());
// Set a new instance of InputManager for testing that uses the IInputManager mock as the
// interface to the server.
@@ -87,17 +95,25 @@
}
}
+ public void addDisplayIdMapping(String uniqueId, int displayId) {
+ mDisplayIdMapping.put(uniqueId, displayId);
+ }
+
private long handleNativeOpenInputDevice(InvocationOnMock inv) {
Objects.requireNonNull(mDevicesChangedListener,
"InputController did not register an InputDevicesChangedListener.");
+ final String phys = inv.getArgument(3);
final InputDevice device = new InputDevice.Builder()
.setId(mDevices.size())
.setName(inv.getArgument(0))
.setVendorId(inv.getArgument(1))
.setProductId(inv.getArgument(2))
- .setDescriptor(inv.getArgument(3))
+ .setDescriptor(phys)
.setExternal(true)
+ .setAssociatedDisplayId(
+ mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+ Display.INVALID_DISPLAY))
.build();
mDevices.add(device);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5442af8..157e893 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -39,6 +39,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -2015,6 +2016,13 @@
eq(virtualDevice), any(), any())).thenReturn(displayId);
virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
NONBLOCKED_APP_PACKAGE_NAME);
+ final String uniqueId = UNIQUE_ID + displayId;
+ doAnswer(inv -> {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = uniqueId;
+ return displayInfo;
+ }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+ mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
}
private ComponentName getPermissionDialogComponent() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 99fa30c..1d3dacc 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -169,14 +169,14 @@
.setEarcSupported(true)
.build();
mHdmiPortInfo[3] =
- new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, 0x3000)
+ new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
.setCecSupported(true)
.setMhlSupported(false)
.setArcSupported(false)
.setEarcSupported(false)
.build();
mHdmiPortInfo[4] =
- new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
+ new HdmiPortInfo.Builder(5, HdmiPortInfo.PORT_OUTPUT, 0x3000)
.setCecSupported(true)
.setMhlSupported(false)
.setArcSupported(false)
@@ -841,6 +841,65 @@
}
@Test
+ public void onHotPlugIn_CecDisabledOnTv_CecNotAvailable() {
+ HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+ mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.onHotplug(4, true);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus =
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ // Wait for DevicePowerStatusAction to finish.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+ assertThat(hdmiControlStatusCallback.mCecAvailable).isFalse();
+ }
+
+ @Test
+ public void onHotPlugIn_CecEnabledOnTv_CecAvailable() {
+ HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+ mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.onHotplug(4, true);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus =
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+ Constants.ADDR_TV);
+ HdmiCecMessage reportPowerStatus =
+ HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+ HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ mNativeWrapper.onCecMessage(reportPowerStatus);
+ mTestLooper.dispatchAll();
+
+ assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+ assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue();
+ }
+ @Test
public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() {
// Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
HdmiCecMessage message = HdmiUtils.buildMessage("80:8D:03");
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 8656f60..bf87e3a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -111,6 +111,8 @@
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock
private PermissionManagerServiceInternal mPermissionManager;
+ @Mock
+ private BackgroundInstallControlCallbackHelper mCallbackHelper;
@Captor
private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
@@ -982,5 +984,11 @@
public File getDiskFile() {
return mFile;
}
+
+
+ @Override
+ public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+ return mCallbackHelper;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 3778a32..f1d3ba9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,7 +23,6 @@
import android.content.pm.UserProperties;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Xml;
import androidx.test.filters.MediumTest;
@@ -32,7 +31,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,13 +52,10 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
public class UserManagerServiceUserPropertiesTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
/** Test that UserProperties can properly read the xml information that it writes. */
@Test
public void testWriteReadXml() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(21)
.setStartWithParent(false)
@@ -123,7 +118,6 @@
/** Tests parcelling an object in which all properties are present. */
@Test
public void testParcelUnparcel() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties originalProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.build();
@@ -134,7 +128,6 @@
/** Tests copying a UserProperties object varying permissions. */
@Test
public void testCopyLacksPermissions() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.setStartWithParent(true)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 6cdbc74..3047bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -41,7 +41,6 @@
import android.os.Bundle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
@@ -51,7 +50,6 @@
import com.android.frameworks.servicestests.R;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,11 +71,8 @@
public void setup() {
mResources = InstrumentationRegistry.getTargetContext().getResources();
}
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
public void testUserTypeBuilder_createUserType() {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
final Bundle systemSettings = makeSettingsBundle("s1", "s2");
final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
@@ -207,7 +202,6 @@
@Test
public void testUserTypeBuilder_defaults() {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
UserTypeDetails type = new UserTypeDetails.Builder()
.setName("name") // Required (no default allowed)
.setBaseType(FLAG_FULL) // Required (no default allowed)
@@ -321,7 +315,6 @@
/** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
@Test
public void testUserTypeFactoryCustomize_profile() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9323b48..df2069e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -39,7 +39,6 @@
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
@@ -56,7 +55,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -99,8 +97,6 @@
private UserSwitchWaiter mUserSwitchWaiter;
private UserRemovalWaiter mUserRemovalWaiter;
private int mOriginalCurrentUserId;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() throws Exception {
@@ -172,7 +168,6 @@
@Test
public void testCloneUser() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
assumeCloneEnabled();
UserHandle mainUser = mUserManager.getMainUser();
assumeTrue("Main user is null", mainUser != null);
@@ -229,7 +224,8 @@
.isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
- assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility);
+ assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+ cloneUserProperties.getProfileApiVisibility());
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
@@ -311,7 +307,6 @@
@Test
public void testPrivateProfile() throws Exception {
- mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
UserHandle mainUser = mUserManager.getMainUser();
assumeTrue("Main user is null", mainUser != null);
// Get the default properties for private profile user type.
@@ -353,8 +348,8 @@
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
assertThrows(SecurityException.class,
privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
- assertThrows(SecurityException.class,
- privateProfileUserProperties::getProfileApiVisibility);
+ assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+ privateProfileUserProperties.getProfileApiVisibility());
assertThrows(SecurityException.class,
privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
compareDrawables(mUserManager.getUserBadge(),
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 3218586..24abc18 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.uri;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
+
import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX;
import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
@@ -57,22 +59,49 @@
import android.net.Uri;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArraySet;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
+import java.util.List;
import java.util.Set;
+@RunWith(Parameterized.class)
public class UriGrantsManagerServiceTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ /**
+ * Why this class needs to test all combinations of
+ * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}:
+ *
+ * <p>Although tests in this class don't directly query the flag, its value
+ * is needed for {@link UriGrantsManagerInternal#checkGrantUriPermissionFromIntent}. This is
+ * particularly important for host side tests (Ravenwood), which cannot read flag values from
+ * the device and must have them set explicitly.
+ */
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getFlags() {
+ return FlagsParameterization.allCombinationsOf(
+ android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS);
+ }
+
+ public UriGrantsManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT, flags);
+ }
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule;
+
private UriGrantsMockContext mContext;
private UriGrantsManagerInternal mService;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f6cf4da..77be01c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -323,6 +323,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -12971,6 +12972,35 @@
}
@Test
+ public void fixNotification_customAllowlistToken()
+ throws Exception {
+ Notification n = new Notification.Builder(mContext, "test")
+ .build();
+ try {
+ Field allowlistToken = Class.forName("android.app.Notification").
+ getDeclaredField("mAllowlistToken");
+ allowlistToken.setAccessible(true);
+ allowlistToken.set(n, new Binder());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+
+ IBinder actual = null;
+ try {
+ Field allowlistToken = Class.forName("android.app.Notification").
+ getDeclaredField("mAllowlistToken");
+ allowlistToken.setAccessible(true);
+ actual = (IBinder) allowlistToken.get(n);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ assertTrue(mService.ALLOWLIST_TOKEN == actual);
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
.thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 90493d4..295b124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -346,6 +346,7 @@
doReturn(true).when(amInternal).hasStartedUserState(anyInt());
doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt());
doReturn(false).when(amInternal).isActivityStartsLoggingEnabled();
+ doReturn(true).when(amInternal).getThemeOverlayReadiness();
LocalServices.addService(ActivityManagerInternal.class, amInternal);
final ActivityManagerService amService =
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 331caa1..7ad26c9 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,7 +16,10 @@
package android.telecom;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.media.ToneGenerator;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,6 +28,8 @@
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
+import com.android.server.telecom.flags.Flags;
+
import java.util.Objects;
/**
@@ -169,7 +174,9 @@
}
/**
- * Creates a new DisconnectCause instance.
+ * Creates a new DisconnectCause instance. This is used by Telephony to pass in extra debug
+ * info to Telecom regarding the disconnect cause.
+ *
* @param code The code for the disconnect cause.
* @param label The localized label to show to the user to explain the disconnect.
* @param description The localized description to show to the user to explain the disconnect.
@@ -180,7 +187,10 @@
* @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
* @hide
*/
- public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public DisconnectCause(int code, @NonNull CharSequence label,
+ @NonNull CharSequence description, @NonNull String reason,
int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
@Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
@Nullable ImsReasonInfo imsReasonInfo) {
@@ -241,28 +251,40 @@
}
/**
- * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+ * Returns the telephony {@link android.telephony.DisconnectCause} for the call. This is only
+ * used internally by Telecom for providing extra debug information from Telephony.
+ *
* @return The disconnect cause.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
return mTelephonyDisconnectCause;
}
/**
- * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+ * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call. This is
+ * only used internally by Telecom for providing extra debug information from Telephony.
+ *
* @return The precise disconnect cause.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
return mTelephonyPreciseDisconnectCause;
}
/**
- * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+ * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This is
+ * only used internally by Telecom for providing extra debug information from Telephony.
+ *
* @return The {@link ImsReasonInfo} or {@code null} if not known.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public @Nullable ImsReasonInfo getImsReasonInfo() {
return mImsReasonInfo;
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b7706a9..6bdc43e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -810,6 +810,9 @@
* If this setter method is never called or cleared using
* {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
* {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+ * If this setter is called and set as an empty Set, then this {@link PhoneAccount} does
+ * not support simultaneous calling with any other {@link PhoneAccount}s registered by the
+ * same application.
* <p>
* Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
* were registered by the same application. Simultaneous calling across applications is
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index b59e855..5af2c34 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,6 +39,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.telephony.flags.Flags;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Retention;
@@ -759,6 +762,20 @@
public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
/**
+ * Return the available memory in bytes of the eUICC.
+ *
+ * @param slotId ID of the SIM slot being queried.
+ * @return the available memory in bytes.
+ * @see android.telephony.euicc.EuiccManager#getAvailableMemoryInBytes
+ */
+ @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+ public long onGetAvailableMemoryInBytes(int slotId) {
+ // stub implementation, LPA needs to implement this
+ throw new UnsupportedOperationException("The connected LPA does not implement"
+ + "EuiccService#onGetAvailableMemoryInBytes(int)");
+ }
+
+ /**
* Dump to a provided printWriter.
*/
public void dump(@NonNull PrintWriter printWriter) {
@@ -834,6 +851,22 @@
}
@Override
+ @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+ public void getAvailableMemoryInBytes(
+ int slotId, IGetAvailableMemoryInBytesCallback callback) {
+ mExecutor.execute(
+ () -> {
+ long availableMemoryInBytes =
+ EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+ try {
+ callback.onSuccess(availableMemoryInBytes);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ });
+ }
+
+ @Override
public void startOtaIfNecessary(
int slotId, IOtaStatusChangedCallback statusChangedCallback) {
mExecutor.execute(new Runnable() {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index f8d5ae9..0f8c72b 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -19,6 +19,7 @@
import android.service.euicc.IDeleteSubscriptionCallback;
import android.service.euicc.IDownloadSubscriptionCallback;
import android.service.euicc.IEraseSubscriptionsCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
import android.service.euicc.IGetEidCallback;
@@ -60,4 +61,5 @@
void retainSubscriptionsForFactoryReset(
int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
void dump(in IEuiccServiceDumpResultCallback callback);
-}
\ No newline at end of file
+ void getAvailableMemoryInBytes(int slotId, in IGetAvailableMemoryInBytesCallback callback);
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index 128f58b..bd6d19b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.animation
+package android.service.euicc;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+/** @hide */
+oneway interface IGetAvailableMemoryInBytesCallback {
+ void onSuccess(long availableMemoryInBytes);
+}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 0f54e8d..3c11da5 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -90,7 +90,7 @@
*/
@SystemApi
@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
-public class DomainSelectionService extends Service {
+public abstract class DomainSelectionService extends Service {
private static final String LOG_TAG = "DomainSelectionService";
@@ -152,7 +152,7 @@
private boolean mIsExitedFromAirplaneMode;
private @Nullable ImsReasonInfo mImsReasonInfo;
private @PreciseDisconnectCauses int mCause;
- private @Nullable EmergencyRegResult mEmergencyRegResult;
+ private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
/**
* @param slotIndex The logical slot index.
@@ -172,7 +172,7 @@
@Nullable Uri address, @SelectorType int selectorType,
boolean video, boolean emergency, boolean isTest, boolean exited,
@Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
- @Nullable EmergencyRegResult regResult) {
+ @Nullable EmergencyRegistrationResult regResult) {
mSlotIndex = slotIndex;
mSubId = subscriptionId;
mCallId = callId;
@@ -184,7 +184,7 @@
mIsExitedFromAirplaneMode = exited;
mImsReasonInfo = imsReasonInfo;
mCause = cause;
- mEmergencyRegResult = regResult;
+ mEmergencyRegistrationResult = regResult;
}
/**
@@ -204,7 +204,7 @@
mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
mImsReasonInfo = s.mImsReasonInfo;
mCause = s.mCause;
- mEmergencyRegResult = s.mEmergencyRegResult;
+ mEmergencyRegistrationResult = s.mEmergencyRegistrationResult;
}
/**
@@ -296,8 +296,8 @@
/**
* @return The current registration state of cellular network.
*/
- public @Nullable EmergencyRegResult getEmergencyRegResult() {
- return mEmergencyRegResult;
+ public @Nullable EmergencyRegistrationResult getEmergencyRegistrationResult() {
+ return mEmergencyRegistrationResult;
}
@Override
@@ -313,7 +313,7 @@
+ ", airplaneMode=" + mIsExitedFromAirplaneMode
+ ", reasonInfo=" + mImsReasonInfo
+ ", cause=" + mCause
- + ", regResult=" + mEmergencyRegResult
+ + ", regResult=" + mEmergencyRegistrationResult
+ " }";
}
@@ -331,14 +331,15 @@
&& mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
&& equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
&& mCause == that.mCause
- && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+ && equalsHandlesNulls(mEmergencyRegistrationResult,
+ that.mEmergencyRegistrationResult);
}
@Override
public int hashCode() {
return Objects.hash(mCallId, mAddress, mImsReasonInfo,
mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode,
- mEmergencyRegResult, mSlotIndex, mSubId, mSelectorType, mCause);
+ mEmergencyRegistrationResult, mSlotIndex, mSubId, mSelectorType, mCause);
}
@Override
@@ -359,7 +360,7 @@
out.writeBoolean(mIsExitedFromAirplaneMode);
out.writeParcelable(mImsReasonInfo, 0);
out.writeInt(mCause);
- out.writeParcelable(mEmergencyRegResult, 0);
+ out.writeParcelable(mEmergencyRegistrationResult, 0);
}
private void readFromParcel(@NonNull Parcel in) {
@@ -376,8 +377,9 @@
mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
android.telephony.ims.ImsReasonInfo.class);
mCause = in.readInt();
- mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
- EmergencyRegResult.class);
+ mEmergencyRegistrationResult = in.readParcelable(
+ EmergencyRegistrationResult.class.getClassLoader(),
+ EmergencyRegistrationResult.class);
}
public static final @NonNull Creator<SelectionAttributes> CREATOR =
@@ -413,7 +415,7 @@
private boolean mIsExitedFromAirplaneMode;
private @Nullable ImsReasonInfo mImsReasonInfo;
private @PreciseDisconnectCauses int mCause;
- private @Nullable EmergencyRegResult mEmergencyRegResult;
+ private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
/**
* Default constructor for Builder.
@@ -430,7 +432,7 @@
* @param callId The call identifier.
* @return The same instance of the builder.
*/
- public @NonNull Builder setCallId(@NonNull String callId) {
+ public @NonNull Builder setCallId(@Nullable String callId) {
mCallId = callId;
return this;
}
@@ -441,7 +443,7 @@
* @param address The dialed address.
* @return The same instance of the builder.
*/
- public @NonNull Builder setAddress(@NonNull Uri address) {
+ public @NonNull Builder setAddress(@Nullable Uri address) {
mAddress = address;
return this;
}
@@ -497,7 +499,7 @@
* @param info The reason why the last PS attempt failed.
* @return The same instance of the builder.
*/
- public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+ public @NonNull Builder setPsDisconnectCause(@Nullable ImsReasonInfo info) {
mImsReasonInfo = info;
return this;
}
@@ -519,8 +521,9 @@
* @param regResult The current registration result for emergency services.
* @return The same instance of the builder.
*/
- public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
- mEmergencyRegResult = regResult;
+ public @NonNull Builder setEmergencyRegistrationResult(
+ @Nullable EmergencyRegistrationResult regResult) {
+ mEmergencyRegistrationResult = regResult;
return this;
}
@@ -532,7 +535,7 @@
return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress,
mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber,
mIsExitedFromAirplaneMode, mImsReasonInfo,
- mCause, mEmergencyRegResult);
+ mCause, mEmergencyRegistrationResult);
}
}
}
@@ -697,7 +700,7 @@
public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
@EmergencyScanType int scanType, boolean resetScan,
@NonNull CancellationSignal signal,
- @NonNull Consumer<EmergencyRegResult> consumer) {
+ @NonNull Consumer<EmergencyRegistrationResult> consumer) {
try {
if (signal != null) signal.setOnCancelListener(this);
mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
@@ -721,17 +724,18 @@
private class IWwanSelectorResultCallbackAdapter
extends IWwanSelectorResultCallback.Stub {
- private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+ private final @NonNull Consumer<EmergencyRegistrationResult> mConsumer;
private final @NonNull Executor mExecutor;
- IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+ IWwanSelectorResultCallbackAdapter(
+ @NonNull Consumer<EmergencyRegistrationResult> consumer,
@NonNull Executor executor) {
mConsumer = consumer;
mExecutor = executor;
}
@Override
- public void onComplete(@NonNull EmergencyRegResult result) {
+ public void onComplete(@NonNull EmergencyRegistrationResult result) {
if (mConsumer == null) return;
executeMethodAsyncNoException(mExecutor,
@@ -759,9 +763,8 @@
* @param attr Required to determine the domain.
* @param callback The callback instance being registered.
*/
- public void onDomainSelection(@NonNull SelectionAttributes attr,
- @NonNull TransportSelectorCallback callback) {
- }
+ public abstract void onDomainSelection(@NonNull SelectionAttributes attr,
+ @NonNull TransportSelectorCallback callback);
/**
* Notifies the change in {@link ServiceState} for a specific logical slot index.
@@ -836,7 +839,7 @@
/** @hide */
@Override
- public @Nullable IBinder onBind(@Nullable Intent intent) {
+ public final @Nullable IBinder onBind(@Nullable Intent intent) {
if (intent == null) return null;
if (SERVICE_INTERFACE.equals(intent.getAction())) {
Log.i(LOG_TAG, "DomainSelectionService Bound.");
@@ -863,7 +866,7 @@
* @return {@link Executor} instance.
* @hide
*/
- public @NonNull Executor getCachedExecutor() {
+ public final @NonNull Executor getCachedExecutor() {
synchronized (mExecutorLock) {
if (mExecutor == null) {
Executor e = onCreateExecutor();
diff --git a/telephony/java/android/telephony/EmergencyRegResult.aidl b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl
similarity index 93%
rename from telephony/java/android/telephony/EmergencyRegResult.aidl
rename to telephony/java/android/telephony/EmergencyRegistrationResult.aidl
index f722962..3056031 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.aidl
+++ b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl
@@ -16,4 +16,4 @@
package android.telephony;
-parcelable EmergencyRegResult;
+parcelable EmergencyRegistrationResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegistrationResult.java
similarity index 91%
rename from telephony/java/android/telephony/EmergencyRegResult.java
rename to telephony/java/android/telephony/EmergencyRegistrationResult.java
index 15579be..7041f5b 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.java
+++ b/telephony/java/android/telephony/EmergencyRegistrationResult.java
@@ -35,7 +35,7 @@
*/
@SystemApi
@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
-public final class EmergencyRegResult implements Parcelable {
+public final class EmergencyRegistrationResult implements Parcelable {
/**
* Indicates the cellular network type of the acquired system.
@@ -101,7 +101,7 @@
* @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
* @hide
*/
- public EmergencyRegResult(
+ public EmergencyRegistrationResult(
@AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
@NetworkRegistrationInfo.RegistrationState int regState,
@NetworkRegistrationInfo.Domain int domain,
@@ -125,7 +125,7 @@
* @param s Source emergency scan result
* @hide
*/
- public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+ public EmergencyRegistrationResult(@NonNull EmergencyRegistrationResult s) {
mAccessNetworkType = s.mAccessNetworkType;
mRegState = s.mRegState;
mDomain = s.mDomain;
@@ -139,9 +139,9 @@
}
/**
- * Construct a EmergencyRegResult object from the given parcel.
+ * Construct a EmergencyRegistrationResult object from the given parcel.
*/
- private EmergencyRegResult(@NonNull Parcel in) {
+ private EmergencyRegistrationResult(@NonNull Parcel in) {
readFromParcel(in);
}
@@ -258,7 +258,7 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- EmergencyRegResult that = (EmergencyRegResult) o;
+ EmergencyRegistrationResult that = (EmergencyRegistrationResult) o;
return mAccessNetworkType == that.mAccessNetworkType
&& mRegState == that.mRegState
&& mDomain == that.mDomain
@@ -311,16 +311,16 @@
mCountryIso = in.readString8();
}
- public static final @NonNull Creator<EmergencyRegResult> CREATOR =
- new Creator<EmergencyRegResult>() {
- @Override
- public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
- return new EmergencyRegResult(in);
- }
+ public static final @NonNull Creator<EmergencyRegistrationResult> CREATOR =
+ new Creator<EmergencyRegistrationResult>() {
+ @Override
+ public EmergencyRegistrationResult createFromParcel(@NonNull Parcel in) {
+ return new EmergencyRegistrationResult(in);
+ }
- @Override
- public EmergencyRegResult[] newArray(int size) {
- return new EmergencyRegResult[size];
- }
- };
+ @Override
+ public EmergencyRegistrationResult[] newArray(int size) {
+ return new EmergencyRegistrationResult[size];
+ }
+ };
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a188581..82ed340 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -40,6 +40,7 @@
import android.telephony.SubscriptionManager.ProfileClass;
import android.telephony.SubscriptionManager.SimDisplayNameSource;
import android.telephony.SubscriptionManager.SubscriptionType;
+import android.telephony.SubscriptionManager.TransferStatus;
import android.telephony.SubscriptionManager.UsageSetting;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -236,6 +237,11 @@
@UsageSetting
private final int mUsageSetting;
+ /**
+ * Subscription's transfer status
+ */
+ private final int mTransferStatus;
+
// Below are the fields that do not exist in the database.
/**
@@ -393,6 +399,7 @@
this.mUsageSetting = usageSetting;
this.mIsOnlyNonTerrestrialNetwork = false;
this.mServiceCapabilities = 0;
+ this.mTransferStatus = 0;
}
/**
@@ -433,6 +440,7 @@
this.mUsageSetting = builder.mUsageSetting;
this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
this.mServiceCapabilities = builder.mServiceCapabilities;
+ this.mTransferStatus = builder.mTransferStatus;
}
/**
@@ -928,6 +936,19 @@
return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
}
+ /**
+ * Get the transfer status for this subscription.
+ *
+ * @return The transfer status for this subscription.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public @TransferStatus int getTransferStatus() {
+ return mTransferStatus;
+ }
+
@NonNull
public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
new Parcelable.Creator<SubscriptionInfo>() {
@@ -967,6 +988,7 @@
.setOnlyNonTerrestrialNetwork(source.readBoolean())
.setServiceCapabilities(
SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
+ .setTransferStatus(source.readInt())
.build();
}
@@ -1010,6 +1032,7 @@
dest.writeInt(mUsageSetting);
dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
dest.writeInt(mServiceCapabilities);
+ dest.writeInt(mTransferStatus);
}
@Override
@@ -1075,6 +1098,7 @@
+ " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+ " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
mServiceCapabilities).toString()
+ + " transferStatus=" + mTransferStatus
+ "]";
}
@@ -1101,7 +1125,8 @@
that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
&& mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
&& mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
- && mServiceCapabilities == that.mServiceCapabilities;
+ && mServiceCapabilities == that.mServiceCapabilities
+ && mTransferStatus == that.mTransferStatus;
}
@Override
@@ -1110,7 +1135,8 @@
mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
- mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
+ mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities,
+ mTransferStatus);
result = 31 * result + Arrays.hashCode(mEhplmns);
result = 31 * result + Arrays.hashCode(mHplmns);
result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1314,6 +1340,8 @@
*/
private boolean mIsOnlyNonTerrestrialNetwork = false;
+ private int mTransferStatus = 0;
+
/**
* Service capabilities bitmasks the subscription supports.
*/
@@ -1363,6 +1391,7 @@
mUsageSetting = info.mUsageSetting;
mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
mServiceCapabilities = info.mServiceCapabilities;
+ mTransferStatus = info.mTransferStatus;
}
/**
@@ -1785,6 +1814,18 @@
mServiceCapabilities = combinedCapabilities;
return this;
}
+ /**
+ * Set subscription's transfer status
+ *
+ * @param status Subscription's transfer status
+ * @return The builder.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @NonNull
+ public Builder setTransferStatus(@TransferStatus int status) {
+ mTransferStatus = status;
+ return this;
+ }
/**
* Build the {@link SubscriptionInfo}.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1749545..3fde3b6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1148,6 +1148,14 @@
*/
public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
+ /**
+ * TelephonyProvider column name to identify eSIM's transfer status.
+ * By default, it's disabled.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -1453,6 +1461,41 @@
private static final LruCache<Pair<String, Configuration>, Resources> sResourcesCache =
new LruCache<>(MAX_RESOURCE_CACHE_ENTRY_COUNT);
+
+ /**
+ * The profile has not been transferred or converted to an eSIM.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_NONE = 0;
+
+ /**
+ * The existing profile of the old device has been transferred to an eSIM of the new device.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+
+ /**
+ * The existing profile of the same device has been converted to an eSIM of the same device
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_CONVERTED = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"TRANSFER_STATUS"},
+ value = {
+ TRANSFER_STATUS_NONE,
+ TRANSFER_STATUS_TRANSFERRED_OUT,
+ TRANSFER_STATUS_CONVERTED,
+ })
+ public @interface TransferStatus {}
+
+
/**
* A listener class for monitoring changes to {@link SubscriptionInfo} records.
* <p>
@@ -4716,4 +4759,27 @@
public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
return 1 << (capability - 1);
}
+
+ /**
+ * Set the transfer status of the subscriptionInfo of the subId.
+ * @param subscriptionId The unique SubscriptionInfo key in database.
+ * @param status The transfer status to change.
+ *
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void setTransferStatus(int subscriptionId, @TransferStatus int status) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setTransferStatus(subscriptionId, status);
+ }
+ } catch (RemoteException ex) {
+ logd("setTransferStatus for subId = " + subscriptionId + " failed.");
+ throw ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index ea83815..b900af3 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -48,7 +48,8 @@
*/
void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
@EmergencyScanType int scanType, boolean resetScan,
- @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+ @NonNull CancellationSignal signal,
+ @NonNull Consumer<EmergencyRegistrationResult> consumer);
/**
* Notifies the FW that the domain has been selected. After this method is called,
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 9b8e62f..7935d24 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,12 +16,14 @@
package android.telephony.euicc;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.app.Activity;
import android.app.PendingIntent;
@@ -50,6 +52,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -861,6 +864,10 @@
*/
public static final int ERROR_INVALID_PORT = 10017;
+ /** Temporary failure to retrieve available memory because eUICC is not ready. */
+ @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+ public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L;
+
/**
* Apps targeting on Android T and beyond will get exception whenever switchToSubscription
* without portIndex is called for disable subscription.
@@ -961,6 +968,35 @@
}
/**
+ * Returns the available memory in bytes of the eUICC.
+ *
+ * @return the available memory in bytes. May be {@link #EUICC_MEMORY_FIELD_UNAVAILABLE} if the
+ * eUICC is not ready. Check {@link #isEnabled} for more information.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC} or
+ * device doesn't support querying this information from the eUICC.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges"
+ })
+ public long getAvailableMemoryInBytes() {
+ if (!isEnabled()) {
+ return EUICC_MEMORY_FIELD_UNAVAILABLE;
+ }
+ try {
+ return getIEuiccController()
+ .getAvailableMemoryInBytes(mCardId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the current status of eUICC OTA.
*
* <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
@@ -1707,4 +1743,57 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the supported carrier ids for pSIM conversion.
+ *
+ * <p>Any existing pSIM conversion supported carrier list will be replaced
+ * by the {@code carrierIds} set here.
+ *
+ * @param carrierIds is a list of carrierIds that supports pSIM conversion
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+ * @throws IllegalStateException if this method is called when {@link #isEnabled} is false.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void setPsimConversionSupportedCarriers(@NonNull Set<Integer> carrierIds) {
+ if (!isEnabled()) {
+ throw new IllegalStateException("Euicc is not enabled");
+ }
+ try {
+ int[] arr = carrierIds.stream().mapToInt(Integer::intValue).toArray();
+ getIEuiccController().setPsimConversionSupportedCarriers(arr);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Returns whether the given carrier id supports pSIM conversion or not.
+ *
+ * @param carrierId to check whether pSIM conversion is supported or not
+ * @return whether the given carrier id supports pSIM conversion or not,
+ * or false if {@link #isEnabled} is false
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public boolean isPsimConversionSupported(int carrierId) {
+ if (!isEnabled()) {
+ return false;
+ }
+ try {
+ return getIEuiccController().isPsimConversionSupported(carrierId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/satellite/EnableRequestAttributes.java b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java
new file mode 100644
index 0000000..bc9d230
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * EnableRequestAttributes is used to store the attributes of the request
+ * {@link SatelliteManager#requestEnabled(EnableRequestAttributes, Executor, Consumer)}
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public class EnableRequestAttributes {
+ /** {@code true} to enable satellite and {@code false} to disable satellite */
+ private boolean mIsEnabled;
+ /**
+ * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+ * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+ */
+ private boolean mIsDemoMode;
+ /**
+ * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+ * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+ * Telephony.
+ */
+ private boolean mIsEmergencyMode;
+
+ /**
+ * Constructor from builder.
+ *
+ * @param builder Builder of {@link EnableRequestAttributes}.
+ */
+ private EnableRequestAttributes(@NonNull Builder builder) {
+ this.mIsEnabled = builder.mIsEnabled;
+ this.mIsDemoMode = builder.mIsDemoMode;
+ this.mIsEmergencyMode = builder.mIsEmergencyMode;
+ }
+
+ /**
+ * @return Whether satellite is to be enabled
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /**
+ * @return Whether demo mode is to be enabled
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public boolean isDemoMode() {
+ return mIsDemoMode;
+ }
+
+ /**
+ * @return Whether satellite is to be enabled for emergency mode
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public boolean isEmergencyMode() {
+ return mIsEmergencyMode;
+ }
+
+ /**
+ * The builder class of {@link EnableRequestAttributes}
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final class Builder {
+ private boolean mIsEnabled;
+ private boolean mIsDemoMode = false;
+ private boolean mIsEmergencyMode = false;
+
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public Builder(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ }
+
+ /**
+ * Set demo mode
+ *
+ * @param isDemoMode {@code true} to enable demo mode and {@code false} to disable. When
+ * disabling satellite, {@code isDemoMode} is always considered as
+ * {@code false} by Telephony.
+ * @return The builder object
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public Builder setDemoMode(boolean isDemoMode) {
+ if (mIsEnabled) {
+ mIsDemoMode = isDemoMode;
+ }
+ return this;
+ }
+
+ /**
+ * Set emergency mode
+ *
+ * @param isEmergencyMode {@code true} means satellite is enabled for emergency mode,
+ * {@code false} otherwise. When disabling satellite,
+ * {@code isEmergencyMode} is always considered as {@code false} by
+ * Telephony.
+ * @return The builder object
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public Builder setEmergencyMode(boolean isEmergencyMode) {
+ if (mIsEnabled) {
+ mIsEmergencyMode = isEmergencyMode;
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link EnableRequestAttributes}
+ *
+ * @return The {@link EnableRequestAttributes} instance.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public EnableRequestAttributes build() {
+ return new EnableRequestAttributes(this);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b97822a..4a61114 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -162,6 +162,13 @@
/**
* Bundle key to get the response from
+ * {@link #requestIsEmergencyModeEnabled(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_MODE_ENABLED = "emergency_mode_enabled";
+
+ /**
+ * Bundle key to get the response from
* {@link #requestIsSupported(Executor, OutcomeReceiver)}.
* @hide
*/
@@ -341,6 +348,13 @@
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
+ /**
+ * Telephony framework timeout to receive ACK or response from the satellite modem after
+ * sending a request to the modem.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24;
+
/** @hide */
@IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
SATELLITE_RESULT_SUCCESS,
@@ -366,7 +380,8 @@
SATELLITE_RESULT_NOT_SUPPORTED,
SATELLITE_RESULT_REQUEST_IN_PROGRESS,
SATELLITE_RESULT_MODEM_BUSY,
- SATELLITE_RESULT_ILLEGAL_STATE
+ SATELLITE_RESULT_ILLEGAL_STATE,
+ SATELLITE_RESULT_MODEM_TIMEOUT
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteResult {}
@@ -482,20 +497,18 @@
* aligned with the satellite, user can send a message and also receive a reply in demo mode.
* If enableSatellite is {@code false}, enableDemoMode has no impact on the behavior.
*
- * @param enableSatellite {@code true} to enable the satellite modem and
- * {@code false} to disable.
- * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
+ * @param attributes The attributes of the enable request.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- public void requestEnabled(boolean enableSatellite, boolean enableDemoMode,
+ public void requestEnabled(@NonNull EnableRequestAttributes attributes,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ Objects.requireNonNull(attributes);
Objects.requireNonNull(executor);
Objects.requireNonNull(resultListener);
@@ -509,14 +522,17 @@
() -> resultListener.accept(result)));
}
};
- telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
- errorCallback);
+ telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(),
+ attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ Rlog.e(TAG, "requestEnabled() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
- Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
- ex.rethrowAsRuntimeException();
+ Rlog.e(TAG, "requestEnabled() exception: ", ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -566,12 +582,14 @@
};
telephony.requestIsSatelliteEnabled(mSubId, receiver);
} else {
+ loge("requestIsEnabled() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
- loge("requestIsSatelliteEnabled() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("requestIsEnabled() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -621,11 +639,68 @@
};
telephony.requestIsDemoModeEnabled(mSubId, receiver);
} else {
+ loge("requestIsDemoModeEnabled() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsDemoModeEnabled() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
+ /**
+ * Request to get whether the satellite service is enabled for emergency mode.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return a {@code boolean} with value {@code true} if satellite is enabled
+ * for emergency mode and {@code false} otherwise.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteResult}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void requestIsEmergencyModeEnabled(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_EMERGENCY_MODE_ENABLED)) {
+ boolean isEmergencyModeEnabled =
+ resultData.getBoolean(KEY_EMERGENCY_MODE_ENABLED);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isEmergencyModeEnabled)));
+ } else {
+ loge("KEY_EMERGENCY_MODE_ENABLED does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestIsEmergencyModeEnabled(mSubId, receiver);
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestIsEmergencyModeEnabled() RemoteException: " + ex);
ex.rethrowAsRuntimeException();
}
}
@@ -678,12 +753,14 @@
};
telephony.requestIsSatelliteSupported(mSubId, receiver);
} else {
+ loge("requestIsSupported() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
- loge("requestIsSatelliteSupported() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("requestIsSupported() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -733,12 +810,14 @@
};
telephony.requestSatelliteCapabilities(mSubId, receiver);
} else {
+ loge("requestCapabilities() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
- loge("requestSatelliteCapabilities() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("requestCapabilities() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -1014,12 +1093,14 @@
telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
internalCallback);
} else {
+ loge("startTransmissionUpdates() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
- loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("startTransmissionUpdates() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1069,12 +1150,14 @@
() -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
}
} else {
+ loge("stopTransmissionUpdates() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
- loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("stopTransmissionUpdates() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1092,7 +1175,6 @@
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1119,12 +1201,14 @@
cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
errorCallback);
} else {
+ loge("provisionService() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
- loge("provisionSatelliteService() RemoteException=" + ex);
- ex.rethrowAsRuntimeException();
+ loge("provisionService() RemoteException=" + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(cancelRemote);
@@ -1168,12 +1252,14 @@
};
telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
} else {
+ loge("deprovisionService() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
- loge("deprovisionSatelliteService() RemoteException=" + ex);
- ex.rethrowAsRuntimeException();
+ loge("deprovisionService() RemoteException ex=" + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1215,7 +1301,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
+ loge("registerForProvisionStateChanged() RemoteException: " + ex);
ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
@@ -1302,12 +1388,14 @@
};
telephony.requestIsSatelliteProvisioned(mSubId, receiver);
} else {
+ loge("requestIsProvisioned() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
- loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ loge("requestIsProvisioned() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -1347,7 +1435,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
+ loge("registerForModemStateChanged() RemoteException:" + ex);
ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
@@ -1516,12 +1604,14 @@
};
telephony.pollPendingDatagrams(mSubId, internalCallback);
} else {
+ loge("pollPendingDatagrams() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("pollPendingDatagrams() RemoteException:" + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1573,12 +1663,14 @@
telephony.sendDatagram(mSubId, datagramType, datagram,
needFullScreenPointingUI, internalCallback);
} else {
+ loge("sendDatagram() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("sendDatagram() RemoteException:" + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1628,16 +1720,16 @@
}
}
};
- telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId,
- receiver);
+ telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId, receiver);
} else {
+ loge("requestIsCommunicationAllowedForCurrentLocation() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
- loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: "
- + ex);
- ex.rethrowAsRuntimeException();
+ loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -1688,12 +1780,14 @@
};
telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
} else {
+ loge("requestTimeForNextSatelliteVisibility() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -1720,7 +1814,7 @@
throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException ex) {
- loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
+ loge("setDeviceAlignedWithSatellite() RemoteException:" + ex);
ex.rethrowAsRuntimeException();
}
}
@@ -1830,12 +1924,14 @@
};
telephony.addAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
+ loge("addAttachRestrictionForCarrier() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("addAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1873,12 +1969,14 @@
};
telephony.removeAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
+ loge("removeAttachRestrictionForCarrier() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("removeAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
}
@@ -1939,10 +2037,7 @@
* The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
* signal strength data available.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
- * {@link SatelliteException} with the {@link SatelliteResult}, or return a
- * {@link IllegalStateException} if the Telephony process is not currently available or
- * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
- * has failed.
+ * {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
*/
@@ -1980,12 +2075,14 @@
};
telephony.requestNtnSignalStrength(mSubId, receiver);
} else {
+ loge("requestNtnSignalStrength() invalid telephony");
executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestNtnSignalStrength() RemoteException: " + ex);
- ex.rethrowAsRuntimeException();
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
}
@@ -2187,14 +2284,11 @@
return new ArrayList<>();
}
- private static ITelephony getITelephony() {
+ @Nullable private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
.getTelephonyServiceRegisterer()
.get());
- if (binder == null) {
- throw new RuntimeException("Could not find Telephony Service.");
- }
return binder;
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 3f41d56..cc770aa 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -376,4 +376,12 @@
* @param data with the sim specific configs to be backed up.
*/
void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
+
+ /**
+ * Set the transfer status of the subscriptionInfo that corresponds to subId.
+ * @param subId The unique SubscriptionInfo key in database.
+ * @param status The transfer status to change. This value must be one of the following.
+ */
+ @EnforcePermission("WRITE_EMBEDDED_SUBSCRIPTIONS")
+ void setTransferStatus(int subId, int status);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 213fbc5..bd47b1f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2742,14 +2742,19 @@
* Request to enable or disable the satellite modem.
*
* @param subId The subId of the subscription to enable or disable the satellite modem for.
- * @param enable True to enable the satellite modem and false to disable.
- * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+ * @param enableSatellite True to enable the satellite modem and false to disable.
+ * @param enableDemoMode True if demo mode is enabled and false otherwise. When
+ * disabling satellite, {@code enableDemoMode} is always considered as
+ * {@code false} by Telephony.
+ * @param isEmergency {@code true} means satellite is enabled for emergency mode, {@code false}
+ * otherwise. When disabling satellite, {@code isEmergency} is always
+ * considered as {@code false} by Telephony.
* @param callback The callback to get the result of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
- in IIntegerConsumer callback);
+ void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+ boolean isEmergency, in IIntegerConsumer callback);
/**
* Request to get whether the satellite modem is enabled.
@@ -2775,6 +2780,18 @@
void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
/**
+ * Request to get whether the satellite service is enabled with emergency mode.
+ *
+ * @param subId The subId of the subscription to request whether the satellite demo mode is
+ * enabled for.
+ * @param receiver Result receiver to get the error code of the request and whether the
+ * satellite is enabled with emergency mode.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver);
+
+ /**
* Request to get whether the satellite service is supported on the device.
*
* @param subId The subId of the subscription to check whether satellite is supported for.
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index 0d61fcb..091974a 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -16,8 +16,8 @@
package com.android.internal.telephony;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
oneway interface IWwanSelectorResultCallback {
- void onComplete(in EmergencyRegResult result);
+ void onComplete(in EmergencyRegistrationResult result);
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 07f2916..a9ebd5c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -250,9 +250,6 @@
*/
public static final int DOMAIN_NON_3GPP_PS = 4;
- /** Key to enable comparison of domain selection results from legacy and new code. */
- public static final String EXTRA_COMPARE_DOMAIN = "compare_domain";
-
/** The key to specify the emergency service category */
public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 19f1a5b..053bc7d 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -57,4 +57,7 @@
boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
boolean isCompatChangeEnabled(String callingPackage, long changeId);
+ void setPsimConversionSupportedCarriers(in int[] carrierIds);
+ boolean isPsimConversionSupported(in int carrierId);
+ long getAvailableMemoryInBytes(int cardId, String callingPackage);
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 73cc2f2..f628af1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -340,6 +340,14 @@
wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
}
+ open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
+ val windowRect = getWindowRect(wmHelper)
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ // search and interact with the dismiss button
+ val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+ uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+ }
+
/** Close the pip window by pressing the expand button */
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)