Merge "Added camera_hal to list of native namespaces" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index a25af71..47d3fd5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -18,13 +18,16 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.job.controllers.idle.CarIdlenessTracker;
@@ -89,6 +92,19 @@
}
}
+ @Override
+ public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ mIdleTracker.processConstant(properties, key);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void onBatteryStateChangedLocked() {
+ mIdleTracker.onBatteryStateChanged(
+ mService.isBatteryCharging(), mService.isBatteryNotLow());
+ }
+
/**
* State-change notifications from the idleness tracker
*/
@@ -119,7 +135,16 @@
} else {
mIdleTracker = new DeviceIdlenessTracker();
}
- mIdleTracker.startTracking(ctx, this);
+ mIdleTracker.startTracking(ctx, mService, this);
+ }
+
+ @Override
+ public void dumpConstants(IndentingPrintWriter pw) {
+ pw.println();
+ pw.println("IdleController:");
+ pw.increaseIndent();
+ mIdleTracker.dumpConstants(pw);
+ pw.decreaseIndent();
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
index c458cae..ba0e633 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
@@ -16,10 +16,13 @@
package com.android.server.job.controllers.idle;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.provider.DeviceConfig;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -73,7 +76,8 @@
}
@Override
- public void startTracking(Context context, IdlenessListener listener) {
+ public void startTracking(Context context, JobSchedulerService service,
+ IdlenessListener listener) {
mIdleListener = listener;
IntentFilter filter = new IntentFilter();
@@ -94,6 +98,15 @@
context.registerReceiver(this, filter, null, AppSchedulingModuleThread.getHandler());
}
+ /** Process the specified constant and update internal constants if relevant. */
+ public void processConstant(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ }
+
+ @Override
+ public void onBatteryStateChanged(boolean isCharging, boolean isBatteryNotLow) {
+ }
+
@Override
public void dump(PrintWriter pw) {
pw.print(" mIdle: "); pw.println(mIdle);
@@ -119,6 +132,10 @@
}
@Override
+ public void dumpConstants(IndentingPrintWriter pw) {
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
logIfDebug("Received action: " + action);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
index c943e73..7dd3d13 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
@@ -17,9 +17,12 @@
package com.android.server.job.controllers.idle;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
@@ -27,10 +30,13 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
+import android.provider.DeviceConfig;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService;
@@ -45,17 +51,38 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
+ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+ private static final String IC_DIT_CONSTANT_PREFIX = "ic_dit_";
+ @VisibleForTesting
+ static final String KEY_INACTIVITY_IDLE_THRESHOLD_MS =
+ IC_DIT_CONSTANT_PREFIX + "inactivity_idle_threshold_ms";
+ @VisibleForTesting
+ static final String KEY_INACTIVITY_STABLE_POWER_IDLE_THRESHOLD_MS =
+ IC_DIT_CONSTANT_PREFIX + "inactivity_idle_stable_power_threshold_ms";
+ private static final String KEY_IDLE_WINDOW_SLOP_MS =
+ IC_DIT_CONSTANT_PREFIX + "idle_window_slop_ms";
+
private AlarmManager mAlarm;
private PowerManager mPowerManager;
// After construction, mutations of idle/screen-on/projection states will only happen
// on the JobScheduler thread, either in onReceive(), in an alarm callback, or in on.*Changed.
private long mInactivityIdleThreshold;
+ private long mInactivityStablePowerIdleThreshold;
private long mIdleWindowSlop;
+ /** Stable power is defined as "charging + battery not low." */
+ private boolean mIsStablePower;
private boolean mIdle;
private boolean mScreenOn;
private boolean mDockIdle;
private boolean mProjectionActive;
+
+ /**
+ * Time (in the elapsed realtime timebase) when the idleness check was scheduled. This should
+ * be a negative value if the device is not in state to be considered idle.
+ */
+ private long mIdlenessCheckScheduledElapsed = -1;
+
private IdlenessListener mIdleListener;
private final UiModeManager.OnProjectionStateChangedListener mOnProjectionStateChangedListener =
this::onProjectionStateChanged;
@@ -76,10 +103,14 @@
}
@Override
- public void startTracking(Context context, IdlenessListener listener) {
+ public void startTracking(Context context, JobSchedulerService service,
+ IdlenessListener listener) {
mIdleListener = listener;
mInactivityIdleThreshold = context.getResources().getInteger(
com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
+ mInactivityStablePowerIdleThreshold = context.getResources().getInteger(
+ com.android.internal.R.integer
+ .config_jobSchedulerInactivityIdleThresholdOnStablePower);
mIdleWindowSlop = context.getResources().getInteger(
com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -107,6 +138,46 @@
context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
UiModeManager.PROJECTION_TYPE_ALL, AppSchedulingModuleThread.getExecutor(),
mOnProjectionStateChangedListener);
+
+ mIsStablePower = service.isBatteryCharging() && service.isBatteryNotLow();
+ }
+
+ /** Process the specified constant and update internal constants if relevant. */
+ public void processConstant(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ switch (key) {
+ case KEY_INACTIVITY_IDLE_THRESHOLD_MS:
+ // Keep the threshold in the range [1 minute, 4 hours].
+ mInactivityIdleThreshold = Math.max(MINUTE_IN_MILLIS, Math.min(4 * HOUR_IN_MILLIS,
+ properties.getLong(key, mInactivityIdleThreshold)));
+ // Don't bother updating any pending alarms. Just wait until the next time we
+ // attempt to check for idle state to use the new value.
+ break;
+ case KEY_INACTIVITY_STABLE_POWER_IDLE_THRESHOLD_MS:
+ // Keep the threshold in the range [1 minute, 4 hours].
+ mInactivityStablePowerIdleThreshold = Math.max(MINUTE_IN_MILLIS,
+ Math.min(4 * HOUR_IN_MILLIS,
+ properties.getLong(key, mInactivityStablePowerIdleThreshold)));
+ // Don't bother updating any pending alarms. Just wait until the next time we
+ // attempt to check for idle state to use the new value.
+ break;
+ case KEY_IDLE_WINDOW_SLOP_MS:
+ // Keep the slop in the range [1 minute, 15 minutes].
+ mIdleWindowSlop = Math.max(MINUTE_IN_MILLIS, Math.min(15 * MINUTE_IN_MILLIS,
+ properties.getLong(key, mIdleWindowSlop)));
+ // Don't bother updating any pending alarms. Just wait until the next time we
+ // attempt to check for idle state to use the new value.
+ break;
+ }
+ }
+
+ @Override
+ public void onBatteryStateChanged(boolean isCharging, boolean isBatteryNotLow) {
+ final boolean isStablePower = isCharging && isBatteryNotLow;
+ if (mIsStablePower != isStablePower) {
+ mIsStablePower = isStablePower;
+ maybeScheduleIdlenessCheck("stable power changed");
+ }
}
private void onProjectionStateChanged(@UiModeManager.ProjectionType int activeProjectionTypes,
@@ -134,8 +205,10 @@
public void dump(PrintWriter pw) {
pw.print(" mIdle: "); pw.println(mIdle);
pw.print(" mScreenOn: "); pw.println(mScreenOn);
+ pw.print(" mIsStablePower: "); pw.println(mIsStablePower);
pw.print(" mDockIdle: "); pw.println(mDockIdle);
pw.print(" mProjectionActive: "); pw.println(mProjectionActive);
+ pw.print(" mIdlenessCheckScheduledElapsed: "); pw.println(mIdlenessCheckScheduledElapsed);
}
@Override
@@ -162,6 +235,17 @@
}
@Override
+ public void dumpConstants(IndentingPrintWriter pw) {
+ pw.println("DeviceIdlenessTracker:");
+ pw.increaseIndent();
+ pw.print(KEY_INACTIVITY_IDLE_THRESHOLD_MS, mInactivityIdleThreshold).println();
+ pw.print(KEY_INACTIVITY_STABLE_POWER_IDLE_THRESHOLD_MS, mInactivityStablePowerIdleThreshold)
+ .println();
+ pw.print(KEY_IDLE_WINDOW_SLOP_MS, mIdleWindowSlop).println();
+ pw.decreaseIndent();
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (DEBUG) {
@@ -220,9 +304,24 @@
private void maybeScheduleIdlenessCheck(String reason) {
if ((!mScreenOn || mDockIdle) && !mProjectionActive) {
final long nowElapsed = sElapsedRealtimeClock.millis();
- final long when = nowElapsed + mInactivityIdleThreshold;
+ final long inactivityThresholdMs = mIsStablePower
+ ? mInactivityStablePowerIdleThreshold : mInactivityIdleThreshold;
+ if (mIdlenessCheckScheduledElapsed >= 0) {
+ if (mIdlenessCheckScheduledElapsed + inactivityThresholdMs <= nowElapsed) {
+ if (DEBUG) {
+ Slog.v(TAG, "Previous idle check @ " + mIdlenessCheckScheduledElapsed
+ + " allows device to be idle now");
+ }
+ handleIdleTrigger();
+ return;
+ }
+ } else {
+ mIdlenessCheckScheduledElapsed = nowElapsed;
+ }
+ final long when = mIdlenessCheckScheduledElapsed + inactivityThresholdMs;
if (DEBUG) {
- Slog.v(TAG, "Scheduling idle : " + reason + " now:" + nowElapsed + " when=" + when);
+ Slog.v(TAG, "Scheduling idle : " + reason + " now:" + nowElapsed
+ + " checkElapsed=" + mIdlenessCheckScheduledElapsed + " when=" + when);
}
mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
when, mIdleWindowSlop, "JS idleness",
@@ -232,6 +331,7 @@
private void cancelIdlenessCheck() {
mAlarm.cancel(mIdleAlarmListener);
+ mIdlenessCheckScheduledElapsed = -1;
}
private void handleIdleTrigger() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/IdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/IdlenessTracker.java
index cdab7e5..92ad4df 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/IdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/IdlenessTracker.java
@@ -16,9 +16,14 @@
package com.android.server.job.controllers.idle;
+import android.annotation.NonNull;
import android.content.Context;
+import android.provider.DeviceConfig;
+import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
+import com.android.server.job.JobSchedulerService;
+
import java.io.PrintWriter;
public interface IdlenessTracker {
@@ -29,7 +34,7 @@
* non-interacting state. When the idle state changes thereafter, the given
* listener must be called to report the new state.
*/
- void startTracking(Context context, IdlenessListener listener);
+ void startTracking(Context context, JobSchedulerService service, IdlenessListener listener);
/**
* Report whether the device is currently considered "idle" for purposes of
@@ -40,6 +45,12 @@
*/
boolean isIdle();
+ /** Process the specified constant and update internal constants if relevant. */
+ void processConstant(@NonNull DeviceConfig.Properties properties, @NonNull String key);
+
+ /** Called when the battery state changes. */
+ void onBatteryStateChanged(boolean isCharging, boolean isBatteryNotLow);
+
/**
* Dump useful information about tracked idleness-related state in plaintext.
*/
@@ -49,4 +60,7 @@
* Dump useful information about tracked idleness-related state to proto.
*/
void dump(ProtoOutputStream proto, long fieldId);
+
+ /** Dump any internal constants the tracker may have. */
+ void dumpConstants(IndentingPrintWriter pw);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index c362e95..fec797e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9321,6 +9321,21 @@
field public static final int USER_INTERACTION = 7; // 0x7
}
+ @FlaggedApi("android.app.usage.filter_based_event_query_api") public final class UsageEventsQuery implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getBeginTimeMillis();
+ method public long getEndTimeMillis();
+ method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR;
+ }
+
+ public static final class UsageEventsQuery.Builder {
+ ctor public UsageEventsQuery.Builder(long, long);
+ method @NonNull public android.app.usage.UsageEventsQuery.Builder addEventTypes(@NonNull int...);
+ method @NonNull public android.app.usage.UsageEventsQuery build();
+ }
+
public final class UsageStats implements android.os.Parcelable {
ctor public UsageStats(android.app.usage.UsageStats);
method public void add(android.app.usage.UsageStats);
@@ -9345,6 +9360,7 @@
method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
method public java.util.List<android.app.usage.EventStats> queryEventStats(int, long, long);
method public android.app.usage.UsageEvents queryEvents(long, long);
+ method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
field public static final int INTERVAL_BEST = 4; // 0x4
@@ -18526,11 +18542,13 @@
public class BiometricManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
+ method @FlaggedApi("android.hardware.biometrics.last_authentication_time") @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public long getLastAuthenticationTime(int);
method @NonNull @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public android.hardware.biometrics.BiometricManager.Strings getStrings(int);
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
+ field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
@@ -18576,6 +18594,7 @@
field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa
field public static final int BIOMETRIC_ERROR_VENDOR = 8; // 0x8
+ field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
}
public abstract static class BiometricPrompt.AuthenticationCallback {
@@ -28503,12 +28522,12 @@
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
- method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder enableSafeMode(boolean);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+ method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 79cd373..6a5d07b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3861,7 +3861,7 @@
method public boolean getInstallAsVirtualPreload();
method public int getPendingUserActionReason();
method public boolean getRequestDowngrade();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_INSTALLED_SESSION_PATHS) public String getResolvedBaseApkPath();
+ method @FlaggedApi("android.content.pm.get_resolved_apk_path") @Nullable @RequiresPermission(android.Manifest.permission.READ_INSTALLED_SESSION_PATHS) public String getResolvedBaseApkPath();
method public int getRollbackDataPolicy();
method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a3ebe6e..b5468dc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1935,7 +1935,7 @@
field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
}
- public static final class RingtoneSelection.Builder {
+ @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public static final class RingtoneSelection.Builder {
ctor public RingtoneSelection.Builder();
ctor public RingtoneSelection.Builder(@NonNull android.media.RingtoneSelection);
method @NonNull public android.media.RingtoneSelection build();
diff --git a/core/java/android/app/BackgroundStartPrivileges.java b/core/java/android/app/BackgroundStartPrivileges.java
index 76c0ccf..20278ea 100644
--- a/core/java/android/app/BackgroundStartPrivileges.java
+++ b/core/java/android/app/BackgroundStartPrivileges.java
@@ -174,6 +174,15 @@
@Override
public String toString() {
+ if (this == ALLOW_BAL) {
+ return "BSP.ALLOW_BAL";
+ }
+ if (this == ALLOW_FGS) {
+ return "BSP.ALLOW_FGS";
+ }
+ if (this == NONE) {
+ return "BSP.NONE";
+ }
return "BackgroundStartPrivileges["
+ "allowsBackgroundActivityStarts=" + mAllowsBackgroundActivityStarts
+ ", allowsBackgroundForegroundServiceStarts="
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 2c428ef..1f8784b 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -259,12 +259,10 @@
* @param taskId the id of the task to retrieve the sAutoapshots for
* @param isLowResolution if set, if the snapshot needs to be loaded from disk, this will load
* a reduced resolution of it, which is much faster
- * @param takeSnapshotIfNeeded if set, call {@link #takeTaskSnapshot} to trigger the snapshot
- if no cache exists.
* @return a graphic buffer representing a screenshot of a task
*/
android.window.TaskSnapshot getTaskSnapshot(
- int taskId, boolean isLowResolution, boolean takeSnapshotIfNeeded);
+ int taskId, boolean isLowResolution);
/**
* Requests for a new snapshot to be taken for the task with the given id, storing it in the
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 49543a1..ebd5d64 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -20,10 +20,9 @@
import android.app.usage.BroadcastResponseStats;
import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageEventsQuery;
import android.content.pm.ParceledListSlice;
-import java.util.Map;
-
/**
* System private API for talking with the UsageStatsManagerService.
*
@@ -42,6 +41,8 @@
UsageEvents queryEventsForPackage(long beginTime, long endTime, String callingPackage);
UsageEvents queryEventsForUser(long beginTime, long endTime, int userId, String callingPackage);
UsageEvents queryEventsForPackageForUser(long beginTime, long endTime, int userId, String pkg, String callingPackage);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
+ UsageEvents queryEventsWithFilter(in UsageEventsQuery query, String callingPackage);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void setAppInactive(String packageName, boolean inactive, int userId);
boolean isAppStandbyEnabled();
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index c188686..1eb452c 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -349,6 +349,47 @@
*/
public static final int MAX_EVENT_TYPE = 31;
+ /**
+ * Keep in sync with the event types defined above.
+ * @hide
+ */
+ @IntDef(flag = false, value = {
+ NONE,
+ ACTIVITY_RESUMED,
+ ACTIVITY_PAUSED,
+ END_OF_DAY,
+ CONTINUE_PREVIOUS_DAY,
+ CONFIGURATION_CHANGE,
+ SYSTEM_INTERACTION,
+ USER_INTERACTION,
+ SHORTCUT_INVOCATION,
+ CHOOSER_ACTION,
+ NOTIFICATION_SEEN,
+ STANDBY_BUCKET_CHANGED,
+ NOTIFICATION_INTERRUPTION,
+ SLICE_PINNED_PRIV,
+ SLICE_PINNED,
+ SCREEN_INTERACTIVE,
+ SCREEN_NON_INTERACTIVE,
+ KEYGUARD_SHOWN,
+ KEYGUARD_HIDDEN,
+ FOREGROUND_SERVICE_START,
+ FOREGROUND_SERVICE_STOP,
+ CONTINUING_FOREGROUND_SERVICE,
+ ROLLOVER_FOREGROUND_SERVICE,
+ ACTIVITY_STOPPED,
+ ACTIVITY_DESTROYED,
+ FLUSH_TO_DISK,
+ DEVICE_SHUTDOWN,
+ DEVICE_STARTUP,
+ USER_UNLOCKED,
+ USER_STOPPED,
+ LOCUS_ID_SET,
+ APP_COMPONENT_USED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/core/java/android/app/usage/UsageEventsQuery.aidl b/core/java/android/app/usage/UsageEventsQuery.aidl
new file mode 100644
index 0000000..5ed370d
--- /dev/null
+++ b/core/java/android/app/usage/UsageEventsQuery.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.usage;
+
+parcelable UsageEventsQuery;
\ No newline at end of file
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
new file mode 100644
index 0000000..8c63d18
--- /dev/null
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -0,0 +1,173 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.usage.UsageEvents.Event;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An Object-Oriented representation for a {@link UsageEvents} query.
+ * Used by {@link UsageStatsManager#queryEvents(UsageEventsQuery)} call.
+ */
+@FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
+public final class UsageEventsQuery implements Parcelable {
+ private final @CurrentTimeMillisLong long mBeginTimeMillis;
+ private final @CurrentTimeMillisLong long mEndTimeMillis;
+ private final @Event.EventType int[] mEventTypes;
+
+ private UsageEventsQuery(@NonNull Builder builder) {
+ mBeginTimeMillis = builder.mBeginTimeMillis;
+ mEndTimeMillis = builder.mEndTimeMillis;
+ mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
+ }
+
+ private UsageEventsQuery(Parcel in) {
+ mBeginTimeMillis = in.readLong();
+ mEndTimeMillis = in.readLong();
+ int eventTypesLength = in.readInt();
+ mEventTypes = new int[eventTypesLength];
+ in.readIntArray(mEventTypes);
+ }
+
+ /**
+ * Returns the inclusive timestamp to indicate the beginning of the range of events.
+ * Defined in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+ */
+ public @CurrentTimeMillisLong long getBeginTimeMillis() {
+ return mBeginTimeMillis;
+ }
+
+ /**
+ * Returns the exclusive timpstamp to indicate the end of the range of events.
+ * Defined in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+ */
+ public @CurrentTimeMillisLong long getEndTimeMillis() {
+ return mEndTimeMillis;
+ }
+
+ /**
+ * Returns the set of usage event types for the query.
+ * <em>Note: An empty set indicates query for all usage events. </em>
+ */
+ public @NonNull Set<Integer> getEventTypes() {
+ if (ArrayUtils.isEmpty(mEventTypes)) {
+ return Collections.emptySet();
+ }
+
+ HashSet<Integer> eventTypeSet = new HashSet<>();
+ for (int eventType : mEventTypes) {
+ eventTypeSet.add(eventType);
+ }
+ return eventTypeSet;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mBeginTimeMillis);
+ dest.writeLong(mEndTimeMillis);
+ dest.writeInt(mEventTypes.length);
+ dest.writeIntArray(mEventTypes);
+ }
+
+ @NonNull
+ public static final Creator<UsageEventsQuery> CREATOR =
+ new Creator<UsageEventsQuery>() {
+ @Override
+ public UsageEventsQuery createFromParcel(Parcel in) {
+ return new UsageEventsQuery(in);
+ }
+
+ @Override
+ public UsageEventsQuery[] newArray(int size) {
+ return new UsageEventsQuery[size];
+ }
+ };
+
+ /** @hide */
+ public int[] getEventTypeFilter() {
+ return Arrays.copyOf(mEventTypes, mEventTypes.length);
+ }
+
+ /**
+ * Builder for UsageEventsQuery.
+ */
+ public static final class Builder {
+ private final @CurrentTimeMillisLong long mBeginTimeMillis;
+ private final @CurrentTimeMillisLong long mEndTimeMillis;
+ private final ArraySet<Integer> mEventTypes = new ArraySet<>();
+
+ /**
+ * Constructor that specifies the period for which to return events.
+ * @param beginTimeMillis Inclusive beginning timestamp, as per
+ * {@link java.lang.System#currentTimeMillis()}
+ * @param endTimeMillis Exclusive ending timestamp, as per
+ * {@link java.lang.System#currentTimeMillis()}
+ *
+ * @throws IllegalArgumentException if {@code beginTimeMillis} <
+ * {@code endTimeMillis}
+ */
+ public Builder(@CurrentTimeMillisLong long beginTimeMillis,
+ @CurrentTimeMillisLong long endTimeMillis) {
+ if (beginTimeMillis < 0 || endTimeMillis < beginTimeMillis) {
+ throw new IllegalArgumentException("Invalid period");
+ }
+ mBeginTimeMillis = beginTimeMillis;
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /**
+ * Builds a read-only UsageEventsQuery object.
+ */
+ public @NonNull UsageEventsQuery build() {
+ return new UsageEventsQuery(this);
+ }
+
+ /**
+ * Specifies the list of usage event types to be included in the query.
+ * @param eventTypes List of the usage event types. See {@link UsageEvents.Event}
+ *
+ * @throws llegalArgumentException if the event type is not valid.
+ */
+ public @NonNull Builder addEventTypes(@NonNull @Event.EventType int... eventTypes) {
+ for (int i = 0; i < eventTypes.length; i++) {
+ final int eventType = eventTypes[i];
+ if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
+ throw new IllegalArgumentException("Invalid usage event type: " + eventType);
+ }
+ mEventTypes.add(eventType);
+ }
+ return this;
+ }
+ }
+}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 2a10ed1..4f1c993 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -581,6 +582,29 @@
}
/**
+ * Query for events with specific UsageEventsQuery object.
+ * <em>Note: if the user's device is not in an unlocked state (as defined by
+ * {@link UserManager#isUserUnlocked()}), then {@code null} will be returned.</em>
+ *
+ * @param query The query object used to specify the query parameters.
+ * @return A {@link UsageEvents}.
+ */
+ @FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
+ public UsageEvents queryEvents(@NonNull UsageEventsQuery query) {
+ try {
+ UsageEvents iter = mService.queryEventsWithFilter(query, mContext.getOpPackageName());
+ if (iter != null) {
+ return iter;
+ }
+ } catch (RemoteException e) {
+ // fallthrough and return empty result.
+ }
+ return sEmptyResults;
+ }
+
+ /**
* Like {@link #queryEvents(long, long)}, but only returns events for the calling package.
* <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
* device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index 0b8e29f..a611255 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -28,3 +28,10 @@
description: "Flag for parcelable usage event list"
bug: "301254110"
}
+
+flag {
+ name: "filter_based_event_query_api"
+ namespace: "backstage_power"
+ description: " Feature flag to support filter based event query API"
+ bug: "194321117"
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1114b35..6681e54 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3832,6 +3832,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_INSTALLED_SESSION_PATHS)
+ @FlaggedApi(Flags.FLAG_GET_RESOLVED_APK_PATH)
public @Nullable String getResolvedBaseApkPath() {
return resolvedBaseCodePath;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 36433ce..e2a5747 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1480,6 +1480,7 @@
INSTALL_ALLOW_DOWNGRADE,
INSTALL_STAGED,
INSTALL_REQUEST_UPDATE_OWNERSHIP,
+ INSTALL_IGNORE_DEXOPT_PROFILE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
@@ -1712,6 +1713,18 @@
public static final int INSTALL_ARCHIVED = 1 << 27;
/**
+ * If set, all dexopt profiles are ignored by dexopt during the installation, including the
+ * profile in the DM file and the profile embedded in the APK file. If an invalid profile is
+ * provided during installation, no warning will be reported by {@code adb install}.
+ *
+ * This option does not affect later dexopt operations (e.g., background dexopt and manual `pm
+ * compile` invocations).
+ *
+ * @hide
+ */
+ public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28;
+
+ /**
* Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
* a development-only feature and should not be used on end user devices.
*
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index a8fe21e..814eae6 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -60,7 +60,6 @@
}
flag {
-
name: "rollback_lifetime"
namespace: "package_manager_service"
description: "Feature flag to enable custom rollback lifetime during install."
@@ -74,3 +73,10 @@
description: "Feature flag to improve install freeze time."
bug: "307561242"
}
+
+flag {
+ name: "get_resolved_apk_path"
+ namespace: "package_manager_service"
+ description: "Feature flag to retrieve resolved path of the base APK during an app install."
+ bug: "269728874"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 943eee4..61d8702 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -296,4 +297,15 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT})
@interface LockoutMode {}
+
+ //
+ // Other miscellaneous constants
+ //
+
+ /**
+ * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when there has
+ * been no successful authentication for the given authenticator since boot.
+ */
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ long BIOMETRIC_NO_AUTHENTICATION = -1;
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 82694ee..90bbca8 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -23,6 +23,8 @@
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_BIOMETRIC_MANAGER_CAN_AUTHENTICATE;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +32,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.KeyguardManager;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
@@ -86,6 +89,17 @@
BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
/**
+ * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when no matching
+ * successful authentication has been performed since boot.
+ */
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ public static final long BIOMETRIC_NO_AUTHENTICATION =
+ BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+
+ private static final int GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG;
+
+ /**
* @hide
*/
@IntDef({BIOMETRIC_SUCCESS,
@@ -637,5 +651,58 @@
}
}
+
+ /**
+ * Gets the last time the user successfully authenticated using one of the given authenticators.
+ * The returned value is time in
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} (time since
+ * boot, including sleep).
+ * <p>
+ * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} is returned in the case where there
+ * has been no successful authentication using any of the given authenticators since boot.
+ * <p>
+ * Currently, only {@link Authenticators#DEVICE_CREDENTIAL} and
+ * {@link Authenticators#BIOMETRIC_STRONG} are accepted. {@link IllegalArgumentException} will
+ * be thrown if {@code authenticators} contains other authenticator types.
+ * <p>
+ * Note that this may return successful authentication times even if the device is currently
+ * locked. You may use {@link KeyguardManager#isDeviceLocked()} to determine if the device
+ * is unlocked or not. Additionally, this method may return valid times for an authentication
+ * method that is no longer available. For instance, if the user unlocked the device with a
+ * {@link Authenticators#BIOMETRIC_STRONG} authenticator but then deleted that authenticator
+ * (e.g., fingerprint data), this method will still return the time of that unlock for
+ * {@link Authenticators#BIOMETRIC_STRONG} if it is the most recent successful event. The caveat
+ * is that {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} will be returned if the device
+ * no longer has a secure lock screen at all, even if there were successful authentications
+ * performed before the lock screen was made insecure.
+ *
+ * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+ * @return the time of last authentication or
+ * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION}
+ * @throws IllegalArgumentException if {@code authenticators} contains values other than
+ * {@link Authenticators#DEVICE_CREDENTIAL} and {@link Authenticators#BIOMETRIC_STRONG} or is
+ * 0.
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ @ElapsedRealtimeLong
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ public long getLastAuthenticationTime(
+ @BiometricManager.Authenticators.Types int authenticators) {
+ if (authenticators == 0
+ || (GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS & authenticators) != authenticators) {
+ throw new IllegalArgumentException(
+ "Only BIOMETRIC_STRONG and DEVICE_CREDENTIAL authenticators may be used.");
+ }
+
+ if (mService != null) {
+ try {
+ return mService.getLastAuthenticationTime(UserHandle.myUserId(), authenticators);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c2e5c0b..5bdbe2b5 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -57,6 +57,9 @@
// Checks if biometrics can be used.
int canAuthenticate(String opPackageName, int userId, int authenticators);
+ // Gets the time of last authentication for the given user and authenticators.
+ long getLastAuthenticationTime(int userId, int authenticators);
+
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 18c8d1b..058f302 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -53,6 +53,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
+ // Gets the time of last authentication for the given user and authenticators.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ long getLastAuthenticationTime(int userId, int authenticators);
+
// Checks if any biometrics are enrolled.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 66429e5..979f570 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -1,6 +1,13 @@
package: "android.hardware.biometrics"
flag {
+ name: "last_authentication_time"
+ namespace: "wallet_integration"
+ description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager"
+ bug: "301979982"
+}
+
+flag {
name: "add_key_agreement_crypto_object"
namespace: "biometrics"
description: "Feature flag for adding KeyAgreement api to CryptoObject."
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 779a8db..6f11d3a 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -493,7 +493,7 @@
/**
* Check whether safe mode is enabled
*
- * @see Builder#enableSafeMode(boolean)
+ * @see Builder#setSafeModeEnabled(boolean)
*/
@FlaggedApi(FLAG_SAFE_MODE_CONFIG)
public boolean isSafeModeEnabled() {
@@ -815,8 +815,8 @@
*
* <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
* safe mode. In safe mode, the VCN Network will be torn down and the system will restore
- * connectivity by allowing underlying cellular networks to be used as default. At the same
- * time, VCN will continue to retry until it succeeds.
+ * connectivity by allowing underlying cellular or WiFi networks to be used as default. At
+ * the same time, VCN will continue to retry until it succeeds.
*
* <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
* might not have connectivity, and may not have access to carrier-owned underlying
@@ -826,7 +826,7 @@
*/
@FlaggedApi(FLAG_SAFE_MODE_CONFIG)
@NonNull
- public Builder enableSafeMode(boolean enabled) {
+ public Builder setSafeModeEnabled(boolean enabled) {
mIsSafeModeDisabled = !enabled;
return this;
}
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index be5851c..d84cc2c 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -194,17 +194,22 @@
audioTrack.play();
}
- int count = 0;
- while (count < bytes.length) {
- // Note that we don't take bufferCopy.mOffset into account because
- // it is guaranteed to be 0.
- int written = audioTrack.write(bytes, count, bytes.length);
+ int offset = 0;
+ while (offset < bytes.length) {
+ // Although it requests to write the entire bytes at once, it might fail when the track
+ // got stopped or the thread is interrupted. In that case, it needs to carry on from
+ // last offset.
+ int sizeToWrite = bytes.length - offset;
+ int written = audioTrack.write(bytes, offset, sizeToWrite);
if (written <= 0) {
+ if (written < 0) {
+ Log.e(TAG, "An error occurred while writing to audio track: " + written);
+ }
break;
}
- count += written;
+ offset += written;
}
- return count;
+ return offset;
}
private AudioTrack createStreamingAudioTrack() {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dbce054..862e537 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4177,6 +4177,10 @@
<!-- Inactivity threshold (in milliseconds) used in JobScheduler. JobScheduler will consider
the device to be "idle" after being inactive for this long. -->
<integer name="config_jobSchedulerInactivityIdleThreshold">1860000</integer>
+ <!-- Inactivity threshold (in milliseconds) used in JobScheduler. JobScheduler will consider
+ the device to be "idle" after being inactive for this long if the device is on stable
+ power. Stable power is defined as "charging + battery not low". -->
+ <integer name="config_jobSchedulerInactivityIdleThresholdOnStablePower">1860000</integer>
<!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
<integer name="config_jobSchedulerIdleWindowSlop">300000</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a2f0086..e646548 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2882,6 +2882,7 @@
<java-symbol type="integer" name="config_defaultNightMode" />
<java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
+ <java-symbol type="integer" name="config_jobSchedulerInactivityIdleThresholdOnStablePower" />
<java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
<java-symbol type="bool" name="config_jobSchedulerRestrictBackgroundUser" />
<java-symbol type="integer" name="config_jobSchedulerUserGracePeriod" />
diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
new file mode 100644
index 0000000..839b645
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.app.usage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.usage.UsageEvents.Event;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UsageEventsQueryTest {
+ @Test
+ public void testQueryDuration() {
+ // Test with negative beginTimeMillis.
+ long beginTimeMillis = -100;
+ long endTimeMillis = 100;
+ try {
+ UsageEventsQuery query = new UsageEventsQuery.Builder(beginTimeMillis, endTimeMillis)
+ .build();
+ fail("beginTimeMillis should be a non-negative timestamp measured as the number of"
+ + " milliseconds since 1970-01-01T00:00:00Z.");
+ } catch (IllegalArgumentException e) {
+ // Expected, fall through;
+ }
+
+ // Test with negative endTimeMillis.
+ beginTimeMillis = 1001;
+ endTimeMillis = -1;
+ try {
+ UsageEventsQuery query = new UsageEventsQuery.Builder(beginTimeMillis, endTimeMillis)
+ .build();
+ fail("endTimeMillis should be a non-negative timestamp measured as the number of"
+ + " milliseconds since 1970-01-01T00:00:00Z.");
+ } catch (IllegalArgumentException e) {
+ // Expected, fall through;
+ }
+
+ // Test with beginTimeMillis < endTimeMillis;
+ beginTimeMillis = 2001;
+ endTimeMillis = 1000;
+ try {
+ UsageEventsQuery query = new UsageEventsQuery.Builder(beginTimeMillis, endTimeMillis)
+ .build();
+ fail("beginTimeMillis should be smaller than endTimeMillis");
+ } catch (IllegalArgumentException e) {
+ // Expected, fall through;
+ }
+
+ // Test with beginTimeMillis == endTimeMillis, valid.
+ beginTimeMillis = 1001;
+ endTimeMillis = 1001;
+ try {
+ UsageEventsQuery query = new UsageEventsQuery.Builder(beginTimeMillis, endTimeMillis)
+ .build();
+ assertEquals(query.getBeginTimeMillis(), query.getEndTimeMillis());
+ } catch (IllegalArgumentException e) {
+ // Not expected for valid duration.
+ fail("Valid duration for beginTimeMillis=" + beginTimeMillis
+ + ", endTimeMillis=" + endTimeMillis);
+ }
+
+ beginTimeMillis = 2001;
+ endTimeMillis = 3001;
+ try {
+ UsageEventsQuery query = new UsageEventsQuery.Builder(beginTimeMillis, endTimeMillis)
+ .build();
+ assertEquals(query.getBeginTimeMillis(), 2001);
+ assertEquals(query.getEndTimeMillis(), 3001);
+ } catch (IllegalArgumentException e) {
+ // Not expected for valid duration.
+ fail("Valid duration for beginTimeMillis=" + beginTimeMillis
+ + ", endTimeMillis=" + endTimeMillis);
+ }
+ }
+
+ @Test
+ public void testQueryEventTypes() {
+ Random rnd = new Random();
+ UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
+
+ // Test with invalid event type.
+ int eventType = Event.NONE - 1;
+ try {
+ queryBuilder.addEventTypes(eventType);
+ fail("Invalid event type: " + eventType);
+ } catch (IllegalArgumentException e) {
+ // Expected, fall through.
+ }
+
+ eventType = Event.MAX_EVENT_TYPE + 1;
+ try {
+ queryBuilder.addEventTypes(eventType);
+ fail("Invalid event type: " + eventType);
+ } catch (IllegalArgumentException e) {
+ // Expected, fall through.
+ }
+
+ // Test with valid and duplicate event types.
+ eventType = rnd.nextInt(Event.MAX_EVENT_TYPE + 1);
+ try {
+ UsageEventsQuery query = queryBuilder.addEventTypes(eventType, eventType, eventType)
+ .build();
+ Set<Integer> eventTypeSet = query.getEventTypes();
+ assertEquals(eventTypeSet.size(), 1);
+ int type = eventTypeSet.iterator().next();
+ assertEquals(type, eventType);
+ } catch (IllegalArgumentException e) {
+ fail("Valid event type: " + eventType);
+ }
+ }
+}
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 2d2dd24..b4b3e92 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.security.keymint.HardwareAuthToken;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -37,7 +39,10 @@
public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
- private static IKeystoreAuthorization getService() {
+ /**
+ * @return an instance of IKeystoreAuthorization
+ */
+ public static IKeystoreAuthorization getService() {
return IKeystoreAuthorization.Stub.asInterface(
ServiceManager.checkService("android.security.authorization"));
}
@@ -100,4 +105,24 @@
}
}
+ /**
+ * Gets the last authentication time of the given user and authenticators.
+ *
+ * @param userId user id
+ * @param authenticatorTypes an array of {@link HardwareAuthenticatorType}.
+ * @return the last authentication time or
+ * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
+ */
+ public static long getLastAuthenticationTime(
+ long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+ try {
+ return getService().getLastAuthTime(userId, authenticatorTypes);
+ } catch (RemoteException | NullPointerException e) {
+ Log.w(TAG, "Can not connect to keystore", e);
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ } catch (ServiceSpecificException e) {
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
+
}
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index af188a9..464714f 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -45,8 +45,19 @@
@UnsupportedAppUsage
public static long getSecureUserId() throws IllegalStateException {
+ return getSecureUserId(UserHandle.myUserId());
+ }
+
+ /**
+ * Return the secure user id for a given user id
+ * @param userId the user id, e.g. 0
+ * @return the secure user id or {@link GateKeeper#INVALID_SECURE_USER_ID} if no such mapping
+ * for the given user id is found.
+ * @throws IllegalStateException if there is an error retrieving the secure user id
+ */
+ public static long getSecureUserId(int userId) throws IllegalStateException {
try {
- return getService().getSecureUserId(UserHandle.myUserId());
+ return getService().getSecureUserId(userId);
} catch (RemoteException e) {
throw new IllegalStateException(
"Failed to obtain secure user ID from gatekeeper", e);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index a663f9f..ed99501 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -20,6 +20,7 @@
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -40,12 +41,17 @@
*/
public class WindowExtensionsImpl implements WindowExtensions {
+ private static final String TAG = "WindowExtensionsImpl";
private final Object mLock = new Object();
private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
private volatile SplitController mSplitController;
private volatile WindowAreaComponent mWindowAreaComponent;
+ public WindowExtensionsImpl() {
+ Log.i(TAG, "Initializing Window Extensions.");
+ }
+
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 76f0b67..4973a4d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -156,6 +156,7 @@
public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
+ Log.i(TAG, "Initializing Activity Embedding Controller.");
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
@@ -208,6 +209,7 @@
@Override
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
synchronized (mLock) {
+ Log.i(TAG, "Setting embedding rules. Size: " + rules.size());
mSplitRules.clear();
mSplitRules.addAll(rules);
}
@@ -216,6 +218,7 @@
@Override
public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
synchronized (mLock) {
+ Log.i(TAG, "Request to pin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
if (task == null) {
Log.e(TAG, "Cannot find the task for id: " + taskId);
@@ -272,6 +275,7 @@
@Override
public void unpinTopActivityStack(int taskId){
synchronized (mLock) {
+ Log.i(TAG, "Request to unpin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
if (task == null) {
Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 84feb03..108aa82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -129,9 +129,7 @@
@JvmStatic
fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? {
return if (taskId <= 0) null else try {
- ActivityTaskManager.getService().getTaskSnapshot(
- taskId, isLowResolution, false /* takeSnapshotIfNeeded */
- )
+ ActivityTaskManager.getService().getTaskSnapshot(taskId, isLowResolution)
} catch (e: RemoteException) {
Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e)
null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index ea7b2e9..64294c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -103,6 +103,7 @@
import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -613,14 +614,22 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ HomeTransitionObserver homeTransitionObserver) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
pool, displayController, mainExecutor, mainHandler, animExecutor,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer, homeTransitionObserver);
+ }
+
+ @WMSingleton
+ @Provides
+ static HomeTransitionObserver provideHomeTransitionObserver(Context context,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new HomeTransitionObserver(context, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a533ca5..47769a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -78,6 +78,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -380,9 +381,10 @@
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- Optional<RecentTasksController> recentTasksController) {
+ Optional<RecentTasksController> recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
return new RecentsTransitionHandler(shellInit, transitions,
- recentTasksController.orElse(null));
+ recentTasksController.orElse(null), homeTransitionObserver);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 412a5b5..8e12991 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -343,9 +343,8 @@
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setBounds(task.token, Rect())
- wct.setDensityDpi(task.token, getDefaultDensityDpi())
+ addMoveToSplitChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -827,7 +826,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // Explicitly setting multi-window at task level interferes with animations.
+ // Let task inherit windowing mode once transition is complete instead.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d277eef..c20d23e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -59,6 +59,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
@@ -85,11 +86,15 @@
*/
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+ private final HomeTransitionObserver mHomeTransitionObserver;
+
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
- @Nullable RecentTasksController recentTasksController) {
+ @Nullable RecentTasksController recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
+ mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
shellInit.addInitCallback(() -> {
@@ -911,6 +916,11 @@
Slog.e(TAG, "Duplicate call to finish");
return;
}
+ if (!toHome) {
+ // For some transitions, we may have notified home activity that it became visible.
+ // We need to notify the observer that we are no longer going home.
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
+ "willFinishToHome=%b state=%d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index f561aa5..b528089d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -38,24 +38,15 @@
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
- private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ private SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
mListener;
private @NonNull final Context mContext;
private @NonNull final ShellExecutor mMainExecutor;
- private @NonNull final Transitions mTransitions;
-
public HomeTransitionObserver(@NonNull Context context,
- @NonNull ShellExecutor mainExecutor,
- @NonNull Transitions transitions) {
+ @NonNull ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
- mTransitions = transitions;
-
- mListener = new SingleInstanceRemoteListener<>(this,
- c -> mTransitions.registerObserver(this),
- c -> mTransitions.unregisterObserver(this));
-
}
@Override
@@ -72,7 +63,7 @@
final int mode = change.getMode();
if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
&& TransitionUtil.isOpenOrCloseMode(mode)) {
- mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+ notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
}
}
}
@@ -92,7 +83,14 @@
* Sets the home transition listener that receives any transitions resulting in a change of
*
*/
- public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ public void setHomeTransitionListener(Transitions transitions,
+ IHomeTransitionListener listener) {
+ if (mListener == null) {
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> transitions.registerObserver(this),
+ c -> transitions.unregisterObserver(this));
+ }
+
if (listener != null) {
mListener.register(listener);
} else {
@@ -100,6 +98,16 @@
}
}
+ /**
+ * Notifies the listener that the home visibility has changed.
+ * @param isVisible true when home activity is visible, false otherwise.
+ */
+ public void notifyHomeVisibilityChanged(boolean isVisible) {
+ if (mListener != null) {
+ mListener.call(l -> l.onHomeVisibilityChanged(isVisible));
+ }
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -113,7 +121,7 @@
/**
* Invalidates this controller, preventing future calls to send updates.
*/
- public void invalidate() {
- mTransitions.unregisterObserver(this);
+ public void invalidate(Transitions transitions) {
+ transitions.unregisterObserver(this);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 718c704..ab5c063 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -192,6 +192,8 @@
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+ private HomeTransitionObserver mHomeTransitionObserver;
+
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -267,10 +269,11 @@
@NonNull DisplayController displayController,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
- @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor animExecutor,
+ @NonNull HomeTransitionObserver observer) {
this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), observer);
}
public Transitions(@NonNull Context context,
@@ -283,7 +286,8 @@
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ @NonNull HomeTransitionObserver observer) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -302,6 +306,7 @@
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
+ mHomeTransitionObserver = observer;
}
private void onInit() {
@@ -351,7 +356,7 @@
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
+ return new IShellTransitionsImpl(this);
}
@Override
@@ -1397,12 +1402,9 @@
private static class IShellTransitionsImpl extends IShellTransitions.Stub
implements ExternalInterfaceBinder {
private Transitions mTransitions;
- private final HomeTransitionObserver mHomeTransitionObserver;
- IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
+ IShellTransitionsImpl(Transitions transitions) {
mTransitions = transitions;
- mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
- mTransitions);
}
/**
@@ -1410,7 +1412,7 @@
*/
@Override
public void invalidate() {
- mHomeTransitionObserver.invalidate();
+ mTransitions.mHomeTransitionObserver.invalidate(mTransitions);
mTransitions = null;
}
@@ -1440,7 +1442,8 @@
public void setHomeTransitionListener(IHomeTransitionListener listener) {
executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
(transitions) -> {
- mHomeTransitionObserver.setHomeTransitionListener(listener);
+ transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+ listener);
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 716f8b8..e206039 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -82,6 +82,7 @@
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -238,7 +239,7 @@
mSplitScreenController = splitScreenController;
mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
@Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null && DesktopModeStatus.isEnabled()
@@ -391,10 +392,10 @@
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
- mTaskOperations.closeTask(mTaskToken);
if (isTaskInSplitScreen(mTaskId)) {
- RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId);
- mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
+ mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId);
+ } else {
+ mTaskOperations.closeTask(mTaskToken);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
@@ -417,8 +418,12 @@
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(mTaskId)) {
+ mSplitScreenController.moveTaskToFullscreen(mTaskId);
+ } else {
+ mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
+ }
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.ifPresent(c -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 9dc86db..b1fb0f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -42,11 +42,11 @@
}
override fun onHandleMenuOpened() {
- animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+ animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
}
override fun onHandleMenuClosed() {
- animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+ animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index c31b9e2..3244ebc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -135,19 +135,29 @@
// second task to split.
val home = tapl.workspace.switchToOverview()
ChangeDisplayOrientationRule.setRotation(rotation)
- home.overviewActions.clickSplit()
+ val isGridOnlyOverviewEnabled = tapl.isGridOnlyOverviewEnabled
+ if (isGridOnlyOverviewEnabled) {
+ home.currentTask.tapMenu().tapSplitMenuItem()
+ } else {
+ home.overviewActions.clickSplit()
+ }
val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
if (snapshots == null || snapshots.size < 1) {
error("Fail to find a overview snapshot to split.")
}
- // Find the second task in the upper right corner in split select mode by sorting
- // 'left' in descending order and 'top' in ascending order.
+ // Find the second task in the upper (or bottom for grid only Overview) right corner in
+ // split select mode by sorting 'left' in descending order and 'top' in ascending (or
+ // descending for grid only Overview) order.
snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
t2.getVisibleBounds().left - t1.getVisibleBounds().left
}
snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ if (isGridOnlyOverviewEnabled) {
+ t2.getVisibleBounds().top - t1.getVisibleBounds().top
+ } else {
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
}
snapshots[0].click()
} else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index cc4db22..fff65f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -74,6 +74,7 @@
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -373,7 +374,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 7a8a2a9..ea7c0d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -75,24 +75,24 @@
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final DisplayController mDisplayController = mock(DisplayController.class);
+ private IHomeTransitionListener mListener;
private Transitions mTransition;
+ private HomeTransitionObserver mHomeTransitionObserver;
@Before
public void setUp() {
+ mListener = mock(IHomeTransitionListener.class);
+ when(mListener.asBinder()).thenReturn(mock(IBinder.class));
+
+ mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor);
mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mHomeTransitionObserver);
+ mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
}
@Test
public void testHomeActivityWithOpenModeNotifiesHomeIsVisible() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -101,23 +101,16 @@
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(1)).onHomeVisibilityChanged(true);
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
}
@Test
public void testHomeActivityWithCloseModeNotifiesHomeIsNotVisible() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -126,38 +119,30 @@
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_TO_BACK);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(1)).onHomeVisibilityChanged(false);
+ verify(mListener, times(1)).onHomeVisibilityChanged(false);
}
@Test
public void testNonHomeActivityDoesNotTriggerCallback() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
when(change.getTaskInfo()).thenReturn(taskInfo);
when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
-
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_UNDEFINED, TRANSIT_TO_BACK);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index da83d4c0..4e300d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -146,7 +146,7 @@
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
// One from Transitions, one from RootTaskDisplayAreaOrganizer
verify(shellInit).addInitCallback(any(), eq(t));
verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
@@ -158,7 +158,7 @@
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
@@ -1075,10 +1075,10 @@
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
new RecentsTransitionHandler(shellInit, transitions,
- mock(RecentTasksController.class));
+ mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
@@ -1623,7 +1623,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
index b74b6a3..b7c3721 100644
--- a/media/java/android/media/RingtoneSelection.java
+++ b/media/java/android/media/RingtoneSelection.java
@@ -642,6 +642,7 @@
* allowing the user to configure their selection. Once a selection is stored as a Uri, then
* the RingtoneSelection can be loaded directly using {@link RingtoneSelection#fromUri}.
*/
+ @FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
public static final class Builder {
private Uri mSoundUri;
private Uri mVibrationUri;
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index 5d6aa03..d763f77 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
import com.android.settingslib.spa.framework.common.PageModel
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.getIntArg
import com.android.settingslib.spa.framework.util.getStringArg
import com.android.settingslib.spa.framework.util.navLink
@@ -110,7 +109,7 @@
fun genStringParamPreferenceModel(): PreferenceModel {
return object : PreferenceModel {
override val title = STRING_PARAM_TITLE
- override val summary = stateOf(stringParam!!)
+ override val summary = { stringParam!! }
}
}
@@ -118,7 +117,7 @@
fun genIntParamPreferenceModel(): PreferenceModel {
return object : PreferenceModel {
override val title = INT_PARAM_TITLE
- override val summary = stateOf(intParam!!.toString())
+ override val summary = { intParam!!.toString() }
}
}
@@ -130,7 +129,7 @@
)
return object : PreferenceModel {
override val title = PAGE_TITLE
- override val summary = stateOf(summaryArray.joinToString(", "))
+ override val summary = { summaryArray.joinToString(", ") }
override val onClick = navigator(
SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 50c0eb7..345b47a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -26,7 +26,6 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.widget.preference.Preference
@@ -50,7 +49,7 @@
Preference(remember {
object : PreferenceModel {
override val title = "Some Preference"
- override val summary = stateOf("Some summary")
+ override val summary = { "Some summary" }
}
})
}.build()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
index 43b6d0b..d7de9b4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -99,7 +99,7 @@
ListPreference(remember {
object : ListPreferenceModel {
override val title = "Preferred network type"
- override val enabled = enabled
+ override val enabled = { enabled.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "5G (recommended)"),
ListPreferenceOption(id = 2, text = "LTE"),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
similarity index 94%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 238204a..96de1a7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,6 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
@@ -136,8 +135,8 @@
Preference(
object : PreferenceModel {
override val title = ASYNC_PREFERENCE_TITLE
- override val summary = model.asyncSummary
- override val enabled = model.asyncEnable
+ override val summary = { model.asyncSummary.value }
+ override val enabled = { model.asyncEnable.value }
}
)
}
@@ -170,7 +169,7 @@
Preference(
object : PreferenceModel {
override val title = MANUAL_UPDATE_PREFERENCE_TITLE
- override val summary = manualUpdaterSummary
+ override val summary = { manualUpdaterSummary.value }
override val onClick = { model.manualUpdaterOnClick() }
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.TouchApp)
@@ -205,11 +204,13 @@
createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
.setUiLayoutFn {
val model = PreferencePageModel.create()
- val autoUpdaterSummary = remember { model.getAutoUpdaterSummary() }
+ val autoUpdaterSummary = remember {
+ model.getAutoUpdaterSummary()
+ }.observeAsState(" ")
Preference(
object : PreferenceModel {
override val title = AUTO_UPDATE_PREFERENCE_TITLE
- override val summary = autoUpdaterSummary.observeAsState(" ")
+ override val summary = { autoUpdaterSummary.value }
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Autorenew)
}
@@ -250,12 +251,12 @@
private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
.setUiLayoutFn {
+ val summary = stringResource(R.string.single_line_summary_preference_summary)
Preference(
model = object : PreferenceModel {
override val title: String =
stringResource(R.string.single_line_summary_preference_title)
- override val summary =
- stringResource(R.string.single_line_summary_preference_summary).toState()
+ override val summary = { summary }
},
singleLineSummary = true,
)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
index b67e066..ce0ee18 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,7 +114,7 @@
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "SwitchPreference"
- override val summary = stateOf("With summary")
+ override val summary = { "With summary" }
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
@@ -131,7 +131,7 @@
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "SwitchPreference"
- override val summary = summary
+ override val summary = { summary.value }
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
@@ -144,7 +144,7 @@
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "SwitchPreference"
- override val summary = stateOf("Not changeable")
+ override val summary = { "Not changeable" }
override val changeable = stateOf(false)
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
index a2cd283..fc50745 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
- override val summary = stateOf("With summary")
+ override val summary = { "With summary" }
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
@@ -122,7 +122,7 @@
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
- override val summary = summary
+ override val summary = { summary.value }
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
@@ -135,7 +135,7 @@
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
- override val summary = stateOf("Not changeable")
+ override val summary = { "Not changeable" }
override val changeable = stateOf(false)
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
similarity index 87%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
index aeba6ea..5c5c504 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,8 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
@@ -58,7 +56,7 @@
@Composable
override fun Page(arguments: Bundle?) {
RegularScaffold(title = getTitle(arguments)) {
- var selectedId by rememberSaveable { mutableStateOf(1) }
+ var selectedId by rememberSaveable { mutableIntStateOf(1) }
Spinner(
options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
selectedId = selectedId,
@@ -66,9 +64,7 @@
)
Preference(object : PreferenceModel {
override val title = "Selected id"
- override val summary = remember {
- derivedStateOf { selectedId.toString() }
- }
+ override val summary = { selectedId.toString() }
})
}
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
index 814d4a1..fb65d65 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.util
import android.graphics.Bitmap
import android.graphics.Canvas
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
deleted file mode 100644
index d7f42b3..0000000
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.screenshot
-
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.DisplaySpec
-
-/**
- * The emulations specs for all 8 permutations of:
- * - phone or tablet.
- * - dark of light mode.
- * - portrait or landscape.
- */
-val DeviceEmulationSpec.Companion.PhoneAndTabletFull
- get() = PhoneAndTabletFullSpec
-
-private val PhoneAndTabletFullSpec =
- DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
-
-/**
- * The emulations specs of:
- * - phone + light mode + portrait.
- * - phone + light mode + landscape.
- * - tablet + dark mode + portrait.
- *
- * This allows to test the most important permutations of a screen/layout with only 3
- * configurations.
- */
-val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
- get() = PhoneAndTabletMinimalSpec
-
-private val PhoneAndTabletMinimalSpec =
- DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
- DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
-
-object Displays {
- val Phone =
- DisplaySpec(
- "phone",
- width = 1440,
- height = 3120,
- densityDpi = 560,
- )
-
- val Tablet =
- DisplaySpec(
- "tablet",
- width = 2560,
- height = 1600,
- densityDpi = 320,
- )
-}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
index 25bc098..f5fba7f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.util
import androidx.test.platform.app.InstrumentationRegistry
import platform.test.screenshot.GoldenImagePathManager
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 7a7cf31..3dcefe9 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.util
import androidx.activity.ComponentActivity
import androidx.compose.material3.MaterialTheme
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
index b2e0b18..b74a243 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.button
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
import androidx.compose.material.icons.outlined.WarningAmber
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.button.ActionButton
import com.android.settingslib.spa.widget.button.ActionButtons
import org.junit.Rule
@@ -27,6 +28,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -48,7 +50,7 @@
fun test() {
screenshotRule.screenshotTest("actionButtons") {
val actionButtons = listOf(
- ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index e6decb1..051ef77 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.chart
import androidx.compose.material3.MaterialTheme
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.chart.BarChart
import com.android.settingslib.spa.widget.chart.BarChartData
import com.android.settingslib.spa.widget.chart.BarChartModel
@@ -26,6 +27,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -61,7 +63,7 @@
override val colors = listOf(color)
override val xValueFormatter =
IAxisValueFormatter { value, _ ->
- "${WeekDay.values()[value.toInt()]}"
+ "${WeekDay.entries[value.toInt()]}"
}
override val yValueFormatter =
IAxisValueFormatter { value, _ ->
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
index f9d93f8..3822571 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.chart
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.chart.LineChart
import com.android.settingslib.spa.widget.chart.LineChartData
import com.android.settingslib.spa.widget.chart.LineChartModel
@@ -26,6 +27,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -59,7 +61,7 @@
)
override val xValueFormatter =
IAxisValueFormatter { value, _ ->
- "${WeekDay.values()[value.toInt()]}"
+ "${WeekDay.entries[value.toInt()]}"
}
override val yValueFormatter =
IAxisValueFormatter { value, _ ->
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
index 34ded3c..6dd62ec 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.chart
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.chart.PieChart
import com.android.settingslib.spa.widget.chart.PieChartData
import com.android.settingslib.spa.widget.chart.PieChartModel
@@ -24,6 +25,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -56,4 +58,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
index 91aca05..0ccfc0b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.illustration
+import com.android.settingslib.spa.screenshot.R
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.illustration.Illustration
import com.android.settingslib.spa.widget.illustration.IllustrationModel
import com.android.settingslib.spa.widget.illustration.ResourceType
@@ -24,6 +26,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
index a366b9e..c1d7188 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.MainSwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import org.junit.Rule
@@ -25,6 +26,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
index d72152c..dd6b553 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material.icons.outlined.DisabledByDefault
import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsIcon
@@ -30,6 +30,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -61,18 +62,18 @@
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = SUMMARY.toState()
+ override val summary = { SUMMARY }
})
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = LONG_SUMMARY.toState()
+ override val summary = { LONG_SUMMARY }
})
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = SUMMARY.toState()
- override val enabled = false.toState()
+ override val summary = { SUMMARY }
+ override val enabled = { false }
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
}
@@ -80,7 +81,7 @@
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = SUMMARY.toState()
+ override val summary = { SUMMARY }
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Autorenew)
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
index 5fcaf85..357d815 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.SystemUpdate
import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.ProgressBarPreference
import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
@@ -30,6 +31,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
index 48c922d..fdee7ee 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.SliderPreference
import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
import org.junit.Rule
@@ -26,6 +27,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
index 2c84a8e..a688e11 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AirplanemodeActive
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsIcon
@@ -29,6 +30,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -72,7 +74,7 @@
private fun SampleSwitchPreferenceWithSummary() {
SwitchPreference(object : SwitchPreferenceModel {
override val title = "SwitchPreference"
- override val summary = stateOf("With summary")
+ override val summary = { "With summary" }
override val checked = stateOf(true)
override val onCheckedChange = null
})
@@ -82,7 +84,7 @@
private fun SampleNotChangeableSwitchPreference() {
SwitchPreference(object : SwitchPreferenceModel {
override val title = "SwitchPreference"
- override val summary = stateOf("Not changeable")
+ override val summary = { "Not changeable" }
override val changeable = stateOf(false)
override val checked = stateOf(true)
override val onCheckedChange = null
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
index 2c37212..8f0abc0 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.preference
import androidx.compose.foundation.layout.Column
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
@@ -54,7 +56,7 @@
TwoTargetSwitchPreference(object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
- override val summary = stateOf("With summary")
+ override val summary = { "With summary" }
override val checked = stateOf(true)
override val onCheckedChange = null
}) {}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
index 0a0faf6..fb01f77 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.ui
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.ui.Footer
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
index 0b4d5e4..2867741 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot
+package com.android.settingslib.spa.screenshot.widget.ui
+import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule
import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spa.widget.ui.SpinnerOption
import org.junit.Rule
@@ -23,6 +24,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
@RunWith(Parameterized::class)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 078c925..14af508 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
@@ -137,7 +136,7 @@
val page = pageWithEntry.page
Preference(object : PreferenceModel {
override val title = "${page.debugBrief()} (${pageWithEntry.entries.size})"
- override val summary = page.debugArguments().toState()
+ override val summary = { page.debugArguments() }
override val onClick = navigator(route = ROUTE_PAGE + "/${page.id}")
})
}
@@ -179,8 +178,9 @@
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
override val title = "open page"
- override val enabled = (spaEnvironment.browseActivityClass != null &&
- page.isBrowsable()).toState()
+ override val enabled = {
+ spaEnvironment.browseActivityClass != null && page.isBrowsable()
+ }
override val onClick = openPage(page)
})
EntryList(pageWithEntry.entries)
@@ -196,9 +196,10 @@
RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
- override val enabled = (spaEnvironment.browseActivityClass != null &&
- entry.containerPage().isBrowsable())
- .toState()
+ override val enabled = {
+ spaEnvironment.browseActivityClass != null &&
+ entry.containerPage().isBrowsable()
+ }
override val onClick = openEntry(entry)
})
Text(text = entryContent)
@@ -210,8 +211,9 @@
for (entry in entries) {
Preference(object : PreferenceModel {
override val title = entry.debugBrief()
- override val summary =
- "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
+ override val summary = {
+ "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}"
+ }
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
})
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
index 74f9c9d..a0149da 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -27,8 +27,6 @@
import androidx.compose.material3.RadioButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.IntState
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -37,7 +35,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.SettingsDialog
import com.android.settingslib.spa.widget.ui.SettingsDialogItem
@@ -69,8 +66,8 @@
*
* Disabled [ListPreference] will be displayed in disabled style.
*/
- val enabled: State<Boolean>
- get() = stateOf(true)
+ val enabled: () -> Boolean
+ get() = { true }
val options: List<ListPreferenceOption>
@@ -89,7 +86,7 @@
) {
Column(modifier = Modifier.selectableGroup()) {
for (option in model.options) {
- Radio(option, model.selectedId.intValue, model.enabled.value) {
+ Radio(option, model.selectedId.intValue, model.enabled()) {
dialogOpened = false
model.onIdSelected(it)
}
@@ -100,7 +97,7 @@
Preference(model = remember(model) {
object : PreferenceModel {
override val title = model.title
- override val summary = derivedStateOf {
+ override val summary = {
model.options.find { it.id == model.selectedId.intValue }?.text ?: ""
}
override val icon = model.icon
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 7ecbec7..bb7e857 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,12 @@
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.common.EntryMacro
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.EntryHighlight
import com.android.settingslib.spa.framework.util.wrapOnClickWithLog
import com.android.settingslib.spa.widget.ui.createSettingsIcon
@@ -42,9 +40,9 @@
override fun UiLayout() {
Preference(model = object : PreferenceModel {
override val title: String = this@SimplePreferenceMacro.title
- override val summary = stateOf(this@SimplePreferenceMacro.summary ?: "")
+ override val summary = { this@SimplePreferenceMacro.summary ?: "" }
override val icon = createSettingsIcon(this@SimplePreferenceMacro.icon)
- override val enabled = stateOf(!this@SimplePreferenceMacro.disabled)
+ override val enabled = { !disabled }
override val onClick = navigator(clickRoute)
})
}
@@ -69,8 +67,8 @@
/**
* The summary of this [Preference].
*/
- val summary: State<String>
- get() = stateOf("")
+ val summary: () -> String
+ get() = { "" }
/**
* The icon of this [Preference].
@@ -85,8 +83,8 @@
*
* Disabled [Preference] will be displayed in disabled style.
*/
- val enabled: State<Boolean>
- get() = stateOf(true)
+ val enabled: () -> Boolean
+ get() = { true }
/**
* The on click handler of this [Preference].
@@ -108,10 +106,11 @@
singleLineSummary: Boolean = false,
) {
val onClickWithLog = wrapOnClickWithLog(model.onClick)
- val modifier = remember(model.enabled.value) {
+ val enabled = model.enabled()
+ val modifier = remember(enabled) {
if (onClickWithLog != null) {
Modifier.clickable(
- enabled = model.enabled.value,
+ enabled = enabled,
onClick = onClickWithLog
)
} else Modifier
@@ -119,11 +118,11 @@
EntryHighlight {
BasePreference(
title = model.title,
- summary = { model.summary.value },
+ summary = model.summary,
singleLineSummary = singleLineSummary,
modifier = modifier,
icon = model.icon,
- enabled = { model.enabled.value },
+ enabled = model.enabled,
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index f14f68c..12afe92 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,8 +51,8 @@
/**
* The summary of this [SwitchPreference].
*/
- val summary: State<String>
- get() = stateOf("")
+ val summary: () -> String
+ get() = { "" }
/**
* The icon of this [Preference].
@@ -95,7 +95,7 @@
EntryHighlight {
InternalSwitchPreference(
title = model.title,
- summary = { model.summary.value },
+ summary = model.summary,
icon = model.icon,
checked = model.checked.value,
changeable = model.changeable.value,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
index b8db63c..9866023 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,32 +16,32 @@
package com.android.settingslib.spa.widget.preference
-import com.android.settingslib.spa.framework.util.EntryHighlight
+import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
-import androidx.compose.runtime.State
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.material3.Icon
+import com.android.settingslib.spa.framework.util.EntryHighlight
@Composable
fun TwoTargetButtonPreference(
- title: String,
- summary: State<String>,
- icon: @Composable (() -> Unit)? = null,
- onClick: () -> Unit,
- buttonIcon: ImageVector,
- buttonIconDescription: String,
- onButtonClick: () -> Unit
+ title: String,
+ summary: () -> String,
+ icon: @Composable (() -> Unit)? = null,
+ onClick: () -> Unit,
+ buttonIcon: ImageVector,
+ buttonIconDescription: String,
+ onButtonClick: () -> Unit
) {
EntryHighlight {
TwoTargetPreference(
- title = title,
- summary = summary,
- onClick = onClick,
- icon = icon) {
+ title = title,
+ summary = summary,
+ onClick = onClick,
+ icon = icon,
+ ) {
IconButton(onClick = onButtonClick) {
Icon(imageVector = buttonIcon, contentDescription = buttonIconDescription)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 5663610..e36572f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -35,7 +34,7 @@
@Composable
internal fun TwoTargetPreference(
title: String,
- summary: State<String>,
+ summary: () -> String,
onClick: () -> Unit,
icon: @Composable (() -> Unit)? = null,
widget: @Composable () -> Unit,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
index 997a023..796ac48 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -25,7 +25,6 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.onDialogText
import org.junit.Rule
import org.junit.Test
@@ -92,7 +91,7 @@
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
- override val enabled = stateOf(false)
+ override val enabled = { false }
override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
override val selectedId = mutableIntStateOf(1)
override val onIdSelected: (Int) -> Unit = {}
@@ -154,7 +153,7 @@
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
- override val enabled = enabledState
+ override val enabled = { enabledState.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 06936e1..8c363db 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -34,7 +33,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.toState
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Rule
@@ -65,7 +63,7 @@
Box(Modifier.width(BOX_WIDTH)) {
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = LONG_SUMMARY.toState()
+ override val summary = { LONG_SUMMARY }
})
}
lineHeightDp = with(LocalDensity.current) {
@@ -85,7 +83,7 @@
Preference(
model = object : PreferenceModel {
override val title = TITLE
- override val summary = LONG_SUMMARY.toState()
+ override val summary = { LONG_SUMMARY }
},
singleLineSummary = true,
)
@@ -113,7 +111,7 @@
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = derivedStateOf { count.toString() }
+ override val summary = { count.toString() }
override val onClick: (() -> Unit) = { count++ }
})
}
@@ -128,8 +126,8 @@
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = TITLE
- override val summary = derivedStateOf { count.toString() }
- override val enabled = false.toState()
+ override val summary = { count.toString() }
+ override val enabled = { false }
override val onClick: (() -> Unit) = { count++ }
})
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt
index 2140c07..e6d2401 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,7 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Launch
-import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
import androidx.compose.ui.test.SemanticsMatcher
@@ -49,11 +48,14 @@
@Test
fun data_displayed() {
composeTestRule.setContent {
- ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel {
- override val title = "Title"
- override val progress = 0.2f
- override val icon: ImageVector = Icons.Outlined.Launch
- }, data = "Data")
+ ProgressBarWithDataPreference(
+ model = object : ProgressBarPreferenceModel {
+ override val title = "Title"
+ override val progress = 0.2f
+ override val icon = Icons.AutoMirrored.Outlined.Launch
+ },
+ data = "Data",
+ )
}
composeTestRule.onNodeWithText("Title").assertIsDisplayed()
composeTestRule.onNodeWithText("Data").assertIsDisplayed()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
index 3a2b445..6de1933 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
@@ -9,7 +25,6 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.toState
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
@@ -64,10 +79,10 @@
) {
TwoTargetButtonPreference(
title = TEST_MODEL_TITLE,
- summary = TEST_MODEL_SUMMARY.toState(),
+ summary = { TEST_MODEL_SUMMARY },
onClick = onClick,
buttonIcon = TEST_BUTTON_ICON,
buttonIconDescription = TEST_BUTTON_ICON_DESCRIPTION,
onButtonClick = onButtonClick
)
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
index 16e09ee..09a6e6d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import org.junit.Rule
@@ -50,7 +49,7 @@
Preference(remember {
object : PreferenceModel {
override val title = "Some Preference"
- override val summary = stateOf("Some summary")
+ override val summary = { "Some summary" }
}
})
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index f3ab80c7..9eee6ad 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -19,7 +19,6 @@
import android.content.pm.ApplicationInfo
import android.icu.text.CollationKey
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.template.app.AppListItem
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
@@ -89,7 +88,7 @@
* @return null if no summary should be displayed.
*/
@Composable
- fun getSummary(option: Int, record: T): State<String>? = null
+ fun getSummary(option: Int, record: T): (() -> String)? = null
@Composable
fun AppListItemModel<T>.AppItem() {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 066db34..7c45b64 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -36,7 +36,6 @@
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spa.widget.ui.Spinner
@@ -150,7 +149,7 @@
?.let { group -> CategoryTitle(title = group) }
val appEntry = list[it]
- val summary = getSummary(option, appEntry.record) ?: "".toState()
+ val summary = getSummary(option, appEntry.record) ?: { "" }
remember(appEntry) {
AppListItemModel(appEntry.record, appEntry.label, summary)
}.AppItem()
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItemModel.kt
similarity index 89%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItemModel.kt
index 6d0d7d6..a7c5036 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItemModel.kt
@@ -17,11 +17,9 @@
package com.android.settingslib.spaprivileged.template.app
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -31,7 +29,7 @@
data class AppListItemModel<T : AppRecord>(
val record: T,
val label: String,
- val summary: State<String>,
+ val summary: () -> String,
)
@Composable
@@ -55,6 +53,6 @@
val record = object : AppRecord {
override val app = LocalContext.current.applicationInfo
}
- AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
+ AppListItemModel<AppRecord>(record, "Chrome", { "Allowed" }).AppListItem {}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 17e9708..3ab27367 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -20,8 +20,7 @@
import android.content.pm.ApplicationInfo
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -149,33 +148,27 @@
override fun getSummary(option: Int, record: T) = getSummary(record)
@Composable
- fun getSummary(record: T): State<String> {
+ fun getSummary(record: T): () -> String {
val restrictions = remember(record.app.userId) {
Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
)
}
- val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
- val allowed = listModel.isAllowed(record)
- return remember {
- derivedStateOf {
- RestrictedSwitchPreference.getSummary(
- context = context,
- restrictedMode = restrictedMode.value,
- summaryIfNoRestricted = getSummaryIfNoRestricted(allowed),
- checked = allowed,
- ).value
- }
- }
+ val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
+ val allowed by listModel.isAllowed(record)
+ return RestrictedSwitchPreference.getSummary(
+ context = context,
+ restrictedModeSupplier = { restrictedMode },
+ summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed) },
+ checked = { allowed },
+ )
}
- private fun getSummaryIfNoRestricted(allowed: State<Boolean?>) = derivedStateOf {
- when (allowed.value) {
- true -> context.getString(R.string.app_permission_summary_allowed)
- false -> context.getString(R.string.app_permission_summary_not_allowed)
- null -> context.getPlaceholder()
- }
+ private fun getSummaryIfNoRestricted(allowed: Boolean?): String = when (allowed) {
+ true -> context.getString(R.string.app_permission_summary_allowed)
+ false -> context.getString(R.string.app_permission_summary_not_allowed)
+ null -> context.getPlaceholder()
}
@Composable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
index 50490c0..ac85dd4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -23,7 +23,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
@@ -73,7 +72,7 @@
override val enabled = when (restrictedMode) {
NoRestricted -> model.enabled
- else -> stateOf(false)
+ else -> ({ false })
}
override val onClick = when (restrictedMode) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index 2129403..d17e0c7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -21,8 +21,6 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -75,17 +73,16 @@
internal object RestrictedSwitchPreference {
fun getSummary(
context: Context,
- restrictedMode: RestrictedMode?,
- summaryIfNoRestricted: State<String>,
- checked: State<Boolean?>,
- ): State<String> = when (restrictedMode) {
- is NoRestricted -> summaryIfNoRestricted
- is BaseUserRestricted -> stateOf(
- context.getString(com.android.settingslib.R.string.disabled)
- )
-
- is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
- null -> stateOf(context.getPlaceholder())
+ restrictedModeSupplier: () -> RestrictedMode?,
+ summaryIfNoRestricted: () -> String,
+ checked: () -> Boolean?,
+ ): () -> String = {
+ when (val restrictedMode = restrictedModeSupplier()) {
+ is NoRestricted -> summaryIfNoRestricted()
+ is BaseUserRestricted -> context.getString(com.android.settingslib.R.string.disabled)
+ is BlockedByAdmin -> restrictedMode.getSummary(checked())
+ null -> context.getPlaceholder()
+ }
}
}
@@ -98,9 +95,9 @@
override val summary = RestrictedSwitchPreference.getSummary(
context = context,
- restrictedMode = restrictedMode,
+ restrictedModeSupplier = { restrictedMode },
summaryIfNoRestricted = model.summary,
- checked = model.checked,
+ checked = { model.checked.value },
)
override val checked = when (restrictedMode) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
index 2fd1b10..c29d7c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -158,7 +158,7 @@
override val app = APP
},
label = LABEL,
- summary = stateOf(SUMMARY),
+ summary = { SUMMARY },
)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
index 6e7fc8e..644a2d7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
@@ -183,7 +183,7 @@
override val app = APP
},
label = LABEL,
- summary = stateOf(SUMMARY),
+ summary = { SUMMARY },
)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 457b810..bf0ad0b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.State
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
@@ -27,7 +26,6 @@
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
@@ -72,10 +70,9 @@
fakeRestrictionsProvider.restrictedMode = NoRestricted
val listModel = TestTogglePermissionAppListModel(isAllowed = true)
- val summaryState = getSummary(listModel)
+ val summary = getSummary(listModel)
- assertThat(summaryState.value)
- .isEqualTo(context.getString(R.string.app_permission_summary_allowed))
+ assertThat(summary).isEqualTo(context.getString(R.string.app_permission_summary_allowed))
}
@Test
@@ -83,9 +80,9 @@
fakeRestrictionsProvider.restrictedMode = NoRestricted
val listModel = TestTogglePermissionAppListModel(isAllowed = false)
- val summaryState = getSummary(listModel)
+ val summary = getSummary(listModel)
- assertThat(summaryState.value)
+ assertThat(summary)
.isEqualTo(context.getString(R.string.app_permission_summary_not_allowed))
}
@@ -94,9 +91,9 @@
fakeRestrictionsProvider.restrictedMode = NoRestricted
val listModel = TestTogglePermissionAppListModel(isAllowed = null)
- val summaryState = getSummary(listModel)
+ val summary = getSummary(listModel)
- assertThat(summaryState.value).isEqualTo(context.getPlaceholder())
+ assertThat(summary).isEqualTo(context.getPlaceholder())
}
@Test
@@ -108,7 +105,7 @@
AppListItemModel(
record = listModel.transformItem(APP),
label = LABEL,
- summary = stateOf(SUMMARY),
+ summary = { SUMMARY },
).AppItem()
}
}
@@ -152,12 +149,12 @@
restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
)
- private fun getSummary(listModel: TestTogglePermissionAppListModel): State<String> {
- lateinit var summary: State<String>
+ private fun getSummary(listModel: TestTogglePermissionAppListModel): String {
+ lateinit var summary: () -> String
composeTestRule.setContent {
summary = createInternalAppListModel(listModel).getSummary(record = TestAppRecord(APP))
}
- return summary
+ return summary()
}
private companion object {
diff --git a/packages/SettingsLib/res/drawable/ic_hdmi.xml b/packages/SettingsLib/res/drawable/ic_hdmi.xml
new file mode 100644
index 0000000..c7a553b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hdmi.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M320,880L320,760L200,521L200,280L240,280L240,140Q240,117 258.5,98.5Q277,80 300,80L660,80Q683,80 701.5,98.5Q720,117 720,140L720,280L760,280L760,521L640,760L640,880L320,880ZM300,280L398,280L398,198L432,198L432,280L528,280L528,198L562,198L562,280L660,280L660,140Q660,140 660,140Q660,140 660,140L300,140Q300,140 300,140Q300,140 300,140L300,280ZM378,743L378,743L582,743L582,743L582,743L378,743L378,743ZM378,743L582,743L700,504L700,340L260,340L260,504L378,743Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_tv.xml b/packages/SettingsLib/res/drawable/ic_tv.xml
new file mode 100644
index 0000000..87abaf4
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_tv.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M320,840L320,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,680Q880,713 856.5,736.5Q833,760 800,760L640,760L640,840L320,840ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680L160,680Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_usb.xml b/packages/SettingsLib/res/drawable/ic_usb.xml
new file mode 100644
index 0000000..b5f15ea
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_usb.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,880Q448,880 428,860Q408,840 408,808Q408,786 419,768Q430,750 450,739L450,628L302,628Q278,628 260,610Q242,592 242,568L242,459Q222,450 211,432.39Q200,414.78 200,392.28Q200,360 220,340Q240,320 272,320Q304,320 324,340Q344,360 344,392.41Q344,415 333,432.5Q322,450 302,459L302,568Q302,568 302,568Q302,568 302,568L450,568L450,228L370,228L480,79L590,228L510,228L510,568L658,568Q658,568 658,568Q658,568 658,568L658,464L616,464L616,320L760,320L760,464L718,464L718,568Q718,592 700,610Q682,628 658,628L510,628L510,739Q529.95,749.65 540.97,768.83Q552,788 552,808Q552,840 532,860Q512,880 480,880Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_wired_device.xml b/packages/SettingsLib/res/drawable/ic_wired_device.xml
new file mode 100644
index 0000000..7964c9f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wired_device.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M123,920L123,750Q89,737 64.5,707.5Q40,678 40,643L40,245L123,245L123,70Q123,56 131,48Q139,40 153,40Q167,40 175,48Q183,56 183,70L183,245L266,245L266,643Q266,678 242,707.5Q218,737 183,750L183,920L123,920ZM450,920L450,750Q416,737 391.5,707.5Q367,678 367,643L367,245L450,245L450,70Q450,56 458,48Q466,40 480,40Q494,40 502,48Q510,56 510,70L510,245L593,245L593,643Q593,678 569,707.5Q545,737 510,750L510,920L450,920ZM777,920L777,750Q743,737 718.5,707.5Q694,678 694,643L694,245L777,245L777,70Q777,56 785,48Q793,40 807,40Q821,40 829,48Q837,56 837,70L837,245L920,245L920,643Q920,678 895.5,707.5Q871,737 837,750L837,920L777,920ZM100,489L206,489L206,305L100,305L100,489ZM427,489L533,489L533,305L427,305L427,489ZM754,489L860,489L860,305L754,305L754,489ZM153,489L153,489L153,489L153,489L153,489ZM480,489L480,489L480,489L480,489L480,489ZM807,489L807,489L807,489L807,489L807,489ZM100,489L100,489L206,489L206,489L100,489ZM427,489L427,489L533,489L533,489L427,489ZM754,489L754,489L860,489L860,489L754,489Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 96029c8..1a5acf6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1334,6 +1334,8 @@
<string name="media_transfer_this_device_name" product="default">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
+ <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
@@ -1357,6 +1359,26 @@
<!-- Sub status indicates the device does not support the current media track. [CHAR LIMIT=NONE] -->
<string name="media_output_status_track_unsupported">Can\’t play this media here</string>
+ <!-- Media output switcher. Default subtitle for any output option that is connected if no more information is known [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_connected">Connected</string>
+
+ <!-- TV media output switcher. Title for devices connected through HDMI ARC if no device name is available. [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_arc_fallback_title">HDMI ARC</string>
+ <!-- TV media output switcher. Title for devices connected through HDMI EARC if no device name is available. [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_earc_fallback_title">HDMI eARC</string>
+
+ <!-- TV media output switcher. Subtitle for devices connected through HDMI ARC if a device name is available. [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_arc_subtitle">Connected via ARC</string>
+ <!-- Media output switcher. Subtitle for devices connected through HDMI EARC if a device name is available. [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_earc_subtitle">Connected via eARC</string>
+
+ <!-- TV media output switcher. Title for the default audio output of the device [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_default">TV Default</string>
+ <!-- TV media output switcher. Subtitle for default audio output which is HDMI, e.g. TV dongle [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_hdmi">HDMI Output</string>
+ <!-- TV media output switcher. Subtitle for default audio output which is internal speaker, i.e. panel VTs [CHAR LIMIT=NONE] -->
+ <string name="tv_media_transfer_internal_speakers">Internal Speakers</string>
+
<!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
<string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index 2a28417..cf224dc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -18,11 +18,13 @@
import android.annotation.DrawableRes;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.AudioDeviceInfo;
import android.media.MediaRoute2Info;
import com.android.settingslib.R;
+import com.android.settingslib.media.flags.Flags;
import java.util.Arrays;
import java.util.HashMap;
@@ -31,16 +33,25 @@
/** A util class to get the appropriate icon for different device types. */
public class DeviceIconUtil {
+
+ // A default icon to use if the type is not present in the map.
+ @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
+ @DrawableRes private static final int DEFAULT_ICON_TV = R.drawable.ic_media_speaker_device;
+
// A map from a @AudioDeviceInfo.AudioDeviceType to full device information.
private final Map<Integer, Device> mAudioDeviceTypeToIconMap = new HashMap<>();
// A map from a @MediaRoute2Info.Type to full device information.
private final Map<Integer, Device> mMediaRouteTypeToIconMap = new HashMap<>();
- // A default icon to use if the type is not present in the map.
- @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
- public DeviceIconUtil() {
- List<Device> deviceList =
- Arrays.asList(
+ private final boolean mIsTv;
+
+ public DeviceIconUtil(Context context) {
+ this(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+ }
+
+ public DeviceIconUtil(boolean isTv) {
+ mIsTv = isTv && Flags.enableTvMediaOutputDialog();
+ List<Device> deviceList = Arrays.asList(
new Device(
AudioDeviceInfo.TYPE_USB_DEVICE,
MediaRoute2Info.TYPE_USB_DEVICE,
@@ -52,7 +63,7 @@
new Device(
AudioDeviceInfo.TYPE_USB_ACCESSORY,
MediaRoute2Info.TYPE_USB_ACCESSORY,
- R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_usb : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_DOCK,
MediaRoute2Info.TYPE_DOCK,
@@ -60,29 +71,27 @@
new Device(
AudioDeviceInfo.TYPE_HDMI,
MediaRoute2Info.TYPE_HDMI,
- R.drawable.ic_headphone),
- // TODO: b/306359110 - Put proper iconography for HDMI_ARC type.
+ mIsTv ? R.drawable.ic_tv : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_HDMI_ARC,
MediaRoute2Info.TYPE_HDMI_ARC,
- R.drawable.ic_headphone),
- // TODO: b/306359110 - Put proper iconography for HDMI_EARC type.
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_HDMI_EARC,
MediaRoute2Info.TYPE_HDMI_EARC,
- R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_WIRED_HEADSET,
MediaRoute2Info.TYPE_WIRED_HEADSET,
- R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_wired_device : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
MediaRoute2Info.TYPE_WIRED_HEADPHONES,
- R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_wired_device : R.drawable.ic_headphone),
new Device(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
- R.drawable.ic_smartphone));
+ mIsTv ? R.drawable.ic_tv : R.drawable.ic_smartphone));
for (int i = 0; i < deviceList.size(); i++) {
Device device = deviceList.get(i);
mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
@@ -90,6 +99,10 @@
}
}
+ private int getDefaultIcon() {
+ return mIsTv ? DEFAULT_ICON_TV : DEFAULT_ICON;
+ }
+
/** Returns a drawable for an icon representing the given audioDeviceType. */
public Drawable getIconFromAudioDeviceType(
@AudioDeviceInfo.AudioDeviceType int audioDeviceType, Context context) {
@@ -103,7 +116,7 @@
if (mAudioDeviceTypeToIconMap.containsKey(audioDeviceType)) {
return mAudioDeviceTypeToIconMap.get(audioDeviceType).mIconDrawableRes;
}
- return DEFAULT_ICON;
+ return getDefaultIcon();
}
/** Returns a drawable res ID for an icon representing the given mediaRouteType. */
@@ -113,7 +126,7 @@
if (mMediaRouteTypeToIconMap.containsKey(mediaRouteType)) {
return mMediaRouteTypeToIconMap.get(mediaRouteType).mIconDrawableRes;
}
- return DEFAULT_ICON;
+ return getDefaultIcon();
}
private static class Device {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index c44f66e..80eeab5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -28,15 +28,24 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+import android.Manifest;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
+import com.android.settingslib.media.flags.Flags;
+
+import java.util.List;
/**
* PhoneMediaDevice extends MediaDevice to represents Phone device.
@@ -58,6 +67,7 @@
public static String getSystemRouteNameFromType(
@NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
CharSequence name;
+ boolean isTv = isTv(context);
switch (routeInfo.getType()) {
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
@@ -73,9 +83,32 @@
name = context.getString(R.string.media_transfer_this_device_name);
break;
case TYPE_HDMI:
+ name = context.getString(isTv ? R.string.tv_media_transfer_default :
+ R.string.media_transfer_external_device_name);
+ break;
case TYPE_HDMI_ARC:
+ if (isTv) {
+ String deviceName = getHdmiOutDeviceName(context);
+ if (deviceName != null) {
+ name = deviceName;
+ } else {
+ name = context.getString(R.string.tv_media_transfer_arc_fallback_title);
+ }
+ } else {
+ name = context.getString(R.string.media_transfer_external_device_name);
+ }
+ break;
case TYPE_HDMI_EARC:
- name = context.getString(R.string.media_transfer_external_device_name);
+ if (isTv) {
+ String deviceName = getHdmiOutDeviceName(context);
+ if (deviceName != null) {
+ name = deviceName;
+ } else {
+ name = context.getString(R.string.tv_media_transfer_arc_fallback_title);
+ }
+ } else {
+ name = context.getString(R.string.media_transfer_external_device_name);
+ }
break;
default:
name = context.getString(R.string.media_transfer_default_device_name);
@@ -94,10 +127,15 @@
String packageName,
RouteListingPreference.Item item) {
super(context, info, packageName, item);
- mDeviceIconUtil = new DeviceIconUtil();
+ mDeviceIconUtil = new DeviceIconUtil(mContext);
initDeviceRecord();
}
+ static boolean isTv(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && Flags.enableTvMediaOutputDialog();
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
@@ -111,9 +149,64 @@
return SELECTION_BEHAVIOR_TRANSFER;
}
+ private static String getHdmiOutDeviceName(Context context) {
+ HdmiControlManager hdmiControlManager;
+ if (context.checkCallingOrSelfPermission(Manifest.permission.HDMI_CEC)
+ == PackageManager.PERMISSION_GRANTED) {
+ hdmiControlManager = context.getSystemService(HdmiControlManager.class);
+ } else {
+ Log.w(TAG, "Could not get HDMI device name, android.permission.HDMI_CEC denied");
+ return null;
+ }
+
+ HdmiPortInfo hdmiOutputPortInfo = null;
+ for (HdmiPortInfo hdmiPortInfo : hdmiControlManager.getPortInfo()) {
+ if (hdmiPortInfo.getType() == HdmiPortInfo.PORT_OUTPUT) {
+ hdmiOutputPortInfo = hdmiPortInfo;
+ break;
+ }
+ }
+ if (hdmiOutputPortInfo == null) {
+ return null;
+ }
+ List<HdmiDeviceInfo> connectedDevices = hdmiControlManager.getConnectedDevices();
+ for (HdmiDeviceInfo deviceInfo : connectedDevices) {
+ if (deviceInfo.getPortId() == hdmiOutputPortInfo.getId()) {
+ String deviceName = deviceInfo.getDisplayName();
+ if (deviceName != null && !deviceName.isEmpty()) {
+ return deviceName;
+ }
+ }
+ }
+ return null;
+ }
+
@Override
public String getSummary() {
- return mSummary;
+ if (!isTv(mContext)) {
+ return mSummary;
+ }
+ switch (mRouteInfo.getType()) {
+ case TYPE_BUILTIN_SPEAKER:
+ return mContext.getString(R.string.tv_media_transfer_internal_speakers);
+ case TYPE_HDMI:
+ return mContext.getString(R.string.tv_media_transfer_hdmi);
+ case TYPE_HDMI_ARC:
+ if (getHdmiOutDeviceName(mContext) == null) {
+ // Connection type is already part of the title.
+ return mContext.getString(R.string.tv_media_transfer_connected);
+ }
+ return mContext.getString(R.string.tv_media_transfer_arc_subtitle);
+ case TYPE_HDMI_EARC:
+ if (getHdmiOutDeviceName(mContext) == null) {
+ // Connection type is already part of the title.
+ return mContext.getString(R.string.tv_media_transfer_connected);
+ }
+ return mContext.getString(R.string.tv_media_transfer_earc_subtitle);
+ default:
+ return null;
+ }
+
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
index 72dfc17..5669276 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
@@ -20,131 +20,309 @@
import android.media.AudioDeviceInfo;
import android.media.MediaRoute2Info;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.R;
+import com.android.settingslib.media.flags.Flags;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class DeviceIconUtilTest {
- private final DeviceIconUtil mDeviceIconUtil = new DeviceIconUtil();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setup() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG);
+ }
@Test
public void getIconResIdFromMediaRouteType_usbDevice_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
public void getIconResIdFromMediaRouteType_usbHeadset_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
public void getIconResIdFromMediaRouteType_usbAccessory_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
- public void getIconResIdFromMediaRouteType_dock_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
- .isEqualTo(R.drawable.ic_headphone);
+ public void getIconResIdFromMediaRouteType_tv_usbAccessory_isUsb() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_usb);
}
@Test
- public void getIconResIdFromMediaRouteType_hdmi_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
- .isEqualTo(R.drawable.ic_headphone);
+ public void getIconResIdFromMediaRouteType_dock_isDock() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_dock_device);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_dock_device);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_hdmi() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_hdmi_isTv() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_tv);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_hdmiArc_isHeadphone() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_ARC))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_hdmiArc_isHdmi() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_ARC))
+ .isEqualTo(R.drawable.ic_hdmi);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_hdmiEarc_isHeadphone() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_EARC))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_hdmiEarc_isHdmi() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_EARC))
+ .isEqualTo(R.drawable.ic_hdmi);
}
@Test
public void getIconResIdFromMediaRouteType_wiredHeadset_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_wiredHeadset_isWiredDevice() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_wired_device);
}
@Test
public void getIconResIdFromMediaRouteType_wiredHeadphones_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_wiredHeadphones_isWiredDevice() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_wired_device);
}
@Test
public void getIconResIdFromMediaRouteType_builtinSpeaker_isSmartphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
- .isEqualTo(R.drawable.ic_smartphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_builtinSpeaker_isTv() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_tv);
}
@Test
public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
- .isEqualTo(R.drawable.ic_smartphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromMediaRouteType_tv_unsupportedType_isSpeaker() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_media_speaker_device);
}
@Test
public void getIconResIdFromAudioDeviceType_usbDevice_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
public void getIconResIdFromAudioDeviceType_usbHeadset_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
public void getIconResIdFromAudioDeviceType_usbAccessory_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_headphone);
}
@Test
- public void getIconResIdFromAudioDeviceType_dock_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
- .isEqualTo(R.drawable.ic_headphone);
+ public void getIconResIdFromAudioDeviceType_tv_usbAccessory_isUsb() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
+ .isEqualTo(R.drawable.ic_usb);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_dock_isDock() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_dock_device);
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
+ .isEqualTo(R.drawable.ic_dock_device);
}
@Test
public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_hdmi_isTv() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
+ .isEqualTo(R.drawable.ic_tv);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_hdmiArc_isHeadphone() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_ARC))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_hdmiArc_isHdmi() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_ARC))
+ .isEqualTo(R.drawable.ic_hdmi);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_hdmiEarc_isHeadphone() {
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_EARC))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_hdmiEarc() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_EARC))
+ .isEqualTo(R.drawable.ic_hdmi);
}
@Test
public void getIconResIdFromAudioDeviceType_wiredHeadset_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_wiredHeadset_isWiredDevice() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
+ .isEqualTo(R.drawable.ic_wired_device);
}
@Test
public void getIconResIdFromAudioDeviceType_wiredHeadphones_isHeadphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
- .isEqualTo(R.drawable.ic_headphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_headphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_wiredHeadphones_isWiredDevice() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
+ .isEqualTo(R.drawable.ic_wired_device);
}
@Test
public void getIconResIdFromAudioDeviceType_builtinSpeaker_isSmartphone() {
- assertThat(
- mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
- .isEqualTo(R.drawable.ic_smartphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_builtinSpeaker_isTv() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
+ .isEqualTo(R.drawable.ic_tv);
}
@Test
public void getIconResIdFromAudioDeviceType_unsupportedType_isSmartphone() {
- assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
- .isEqualTo(R.drawable.ic_smartphone);
+ assertThat(new DeviceIconUtil(/* isTv */ false)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_smartphone);
+ }
+
+ @Test
+ public void getIconResIdFromAudioDeviceType_tv_unsupportedType_isSpeaker() {
+ assertThat(new DeviceIconUtil(/* isTv */ true)
+ .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
+ .isEqualTo(R.drawable.ic_media_speaker_device);
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 89c6ecc..88abf69 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -165,6 +165,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "com_android_systemui_shared_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
@@ -443,6 +444,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "com_android_systemui_shared_flags_lib",
"flag-junit-base",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index dc4208e..e842967 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -1,3 +1,31 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/**
+ * These flags are meant only for internal use in SystemUI and its variants.
+ * For shared, cross-process flags, see //frameworks/libs/systemui/aconfig
+ */
+
+package {
+ default_visibility: [
+ "//visibility:override",
+ "//frameworks/base/packages/SystemUI:__subpackages__",
+ ],
+}
+
aconfig_declarations {
name: "com_android_systemui_flags",
package: "com.android.systemui",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 46d418a..3780468 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -14,6 +14,7 @@
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -29,12 +30,9 @@
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-
-object Scenes {
- val Blank = SceneKey(name = "blank")
- val Communal = SceneKey(name = "communal")
-}
+import kotlinx.coroutines.flow.transform
object Communal {
object Elements {
@@ -43,7 +41,7 @@
}
val sceneTransitions = transitions {
- from(Scenes.Blank, to = Scenes.Communal) {
+ from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) {
spec = tween(durationMillis = 500)
translate(Communal.Elements.Content, Edge.Right)
@@ -58,8 +56,14 @@
* handling and transitions before the full Flexiglass layout is ready.
*/
@Composable
-fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
- val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+fun CommunalContainer(
+ modifier: Modifier = Modifier,
+ viewModel: CommunalViewModel,
+) {
+ val currentScene: SceneKey by
+ viewModel.currentScene
+ .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
+ .collectAsState(TransitionSceneKey.Blank)
// Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
var showSceneTransitionLayout by remember { mutableStateOf(true) }
@@ -70,16 +74,19 @@
SceneTransitionLayout(
modifier = modifier.fillMaxSize(),
currentScene = currentScene,
- onChangeScene = setCurrentScene,
+ onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
transitions = sceneTransitions,
) {
- scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+ scene(
+ TransitionSceneKey.Blank,
+ userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal)
+ ) {
BlankScene { showSceneTransitionLayout = false }
}
scene(
- Scenes.Communal,
- userActions = mapOf(Swipe.Right to Scenes.Blank),
+ TransitionSceneKey.Communal,
+ userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank),
) {
CommunalScene(viewModel, modifier = modifier)
}
@@ -121,3 +128,17 @@
) {
Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
}
+
+// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI.
+object TransitionSceneKey {
+ val Blank = CommunalSceneKey.Blank.toTransitionSceneKey()
+ val Communal = CommunalSceneKey.Communal.toTransitionSceneKey()
+}
+
+fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
+ return SceneKey(name = toString(), identity = this)
+}
+
+fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
+ return this.identity as CommunalSceneKey
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
new file mode 100644
index 0000000..82d4239
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+interface EdgeDetector {
+ /**
+ * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
+ * [density] and [orientation].
+ */
+ fun edge(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation,
+ ): Edge?
+}
+
+val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
+
+/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
+class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
+ override fun edge(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation,
+ ): Edge? {
+ val axisSize: Int
+ val axisPosition: Int
+ val topOrLeft: Edge
+ val bottomOrRight: Edge
+ when (orientation) {
+ Orientation.Horizontal -> {
+ axisSize = layoutSize.width
+ axisPosition = position.x
+ topOrLeft = Edge.Left
+ bottomOrRight = Edge.Right
+ }
+ Orientation.Vertical -> {
+ axisSize = layoutSize.height
+ axisPosition = position.y
+ topOrLeft = Edge.Top
+ bottomOrRight = Edge.Bottom
+ }
+ }
+
+ val sizePx = with(density) { size.toPx() }
+ return when {
+ axisPosition <= sizePx -> topOrLeft
+ axisPosition >= axisSize - sizePx -> bottomOrRight
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
index d005413..ae7d8f5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -2,7 +2,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import kotlinx.coroutines.CoroutineScope
interface GestureHandler {
val draggable: DraggableHandler
@@ -10,9 +9,9 @@
}
interface DraggableHandler {
- suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset)
+ fun onDragStarted(startedPosition: Offset, pointersDown: Int = 1)
fun onDelta(pixels: Float)
- suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float)
+ fun onDragStopped(velocity: Float)
}
interface NestedScrollHandler {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
new file mode 100644
index 0000000..97d3fff
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.horizontalDrag
+import androidx.compose.foundation.gestures.verticalDrag
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerId
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.addPointerInputChange
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * Make an element draggable in the given [orientation].
+ *
+ * The main difference with [multiPointerDraggable] and
+ * [androidx.compose.foundation.gestures.draggable] is that [onDragStarted] also receives the number
+ * of pointers that are down when the drag is started. If you don't need this information, you
+ * should use `draggable` instead.
+ *
+ * Note that the current implementation is trivial: we wait for the touch slope on the *first* down
+ * pointer, then we count the number of distinct pointers that are down right before calling
+ * [onDragStarted]. This means that the drag won't start when a first pointer is down (but not
+ * dragged) and a second pointer is down and dragged. This is an implementation detail that might
+ * change in the future.
+ */
+// TODO(b/291055080): Migrate to the Modifier.Node API.
+@Composable
+internal fun Modifier.multiPointerDraggable(
+ orientation: Orientation,
+ enabled: Boolean,
+ startDragImmediately: Boolean,
+ onDragStarted: (startedPosition: Offset, pointersDown: Int) -> Unit,
+ onDragDelta: (Float) -> Unit,
+ onDragStopped: (velocity: Float) -> Unit,
+): Modifier {
+ val onDragStarted by rememberUpdatedState(onDragStarted)
+ val onDragStopped by rememberUpdatedState(onDragStopped)
+ val onDragDelta by rememberUpdatedState(onDragDelta)
+ val startDragImmediately by rememberUpdatedState(startDragImmediately)
+
+ val velocityTracker = remember { VelocityTracker() }
+ val maxFlingVelocity =
+ LocalViewConfiguration.current.maximumFlingVelocity.let { max ->
+ val maxF = max.toFloat()
+ Velocity(maxF, maxF)
+ }
+
+ return this.pointerInput(enabled, orientation, maxFlingVelocity) {
+ if (!enabled) {
+ return@pointerInput
+ }
+
+ val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
+ velocityTracker.resetTracking()
+ onDragStarted(startedPosition, pointersDown)
+ }
+
+ val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
+
+ val onDragEnd: () -> Unit = {
+ val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
+ onDragStopped(
+ when (orientation) {
+ Orientation.Horizontal -> velocity.x
+ Orientation.Vertical -> velocity.y
+ }
+ )
+ }
+
+ val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit = { change, amount ->
+ velocityTracker.addPointerInputChange(change)
+ onDragDelta(amount)
+ }
+
+ detectDragGestures(
+ orientation = orientation,
+ startDragImmediately = { startDragImmediately },
+ onDragStart = onDragStart,
+ onDragEnd = onDragEnd,
+ onDragCancel = onDragCancel,
+ onDrag = onDrag,
+ )
+ }
+}
+
+/**
+ * Detect drag gestures in the given [orientation].
+ *
+ * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
+ * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
+ * 1) starting the gesture immediately without requiring a drag >= touch slope;
+ * 2) passing the number of pointers down to [onDragStart].
+ */
+private suspend fun PointerInputScope.detectDragGestures(
+ orientation: Orientation,
+ startDragImmediately: () -> Boolean,
+ onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit,
+ onDragEnd: () -> Unit,
+ onDragCancel: () -> Unit,
+ onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
+) {
+ awaitEachGesture {
+ val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
+ var overSlop = 0f
+ val drag =
+ if (startDragImmediately()) {
+ initialDown.consume()
+ initialDown
+ } else {
+ val down = awaitFirstDown(requireUnconsumed = false)
+ val onSlopReached = { change: PointerInputChange, over: Float ->
+ change.consume()
+ overSlop = over
+ }
+
+ // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once
+ // it is public.
+ when (orientation) {
+ Orientation.Horizontal ->
+ awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
+ Orientation.Vertical ->
+ awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
+ }
+ }
+
+ if (drag != null) {
+ // Count the number of pressed pointers.
+ val pressed = mutableSetOf<PointerId>()
+ currentEvent.changes.fastForEach { change ->
+ if (change.pressed) {
+ pressed.add(change.id)
+ }
+ }
+
+ onDragStart(drag.position, pressed.size)
+ onDrag(drag, overSlop)
+
+ val successful =
+ when (orientation) {
+ Orientation.Horizontal ->
+ horizontalDrag(drag.id) {
+ onDrag(it, it.positionChange().x)
+ it.consume()
+ }
+ Orientation.Vertical ->
+ verticalDrag(drag.id) {
+ onDrag(it, it.positionChange().y)
+ it.consume()
+ }
+ }
+
+ if (successful) {
+ onDragEnd()
+ } else {
+ onDragCancel()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 9c799b28..3fd6828 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -16,7 +16,6 @@
package com.android.compose.animation.scene
-import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@@ -101,19 +100,3 @@
MovableElement(layoutImpl, scene, key, modifier, content)
}
}
-
-/** The destination scene when swiping up or left from [upOrLeft]. */
-internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Up]
- Orientation.Horizontal -> userActions[Swipe.Left]
- }
-}
-
-/** The destination scene when swiping down or right from [downOrRight]. */
-internal fun Scene.downOrRight(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Down]
- Orientation.Horizontal -> userActions[Swipe.Right]
- }
-}
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 74e66d2..1f38e70 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
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
@@ -37,6 +38,7 @@
* instance by triggering back navigation or by swiping to a new scene.
* @param transitions the definition of the transitions used to animate a change of scene.
* @param state the observable state of this layout.
+ * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
* @param scenes the configuration of the different scenes of this layout.
*/
@Composable
@@ -46,6 +48,7 @@
transitions: SceneTransitions,
modifier: Modifier = Modifier,
state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+ edgeDetector: EdgeDetector = DefaultEdgeDetector,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
@@ -56,15 +59,17 @@
transitions,
state,
density,
+ edgeDetector,
)
}
layoutImpl.onChangeScene = onChangeScene
layoutImpl.transitions = transitions
layoutImpl.density = density
+ layoutImpl.edgeDetector = edgeDetector
+
layoutImpl.setScenes(scenes)
layoutImpl.setCurrentScene(currentScene)
-
layoutImpl.Content(modifier)
}
@@ -191,9 +196,9 @@
}
}
-enum class SwipeDirection {
- Up,
- Down,
- Left,
- Right,
+enum class SwipeDirection(val orientation: Orientation) {
+ Up(Orientation.Vertical),
+ Down(Orientation.Vertical),
+ Left(Orientation.Horizontal),
+ Right(Orientation.Horizontal),
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index a40b299..fd62659 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -37,7 +37,7 @@
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
-import com.android.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEach
import kotlinx.coroutines.channels.Channel
@VisibleForTesting
@@ -47,6 +47,7 @@
transitions: SceneTransitions,
internal val state: SceneTransitionLayoutState,
density: Density,
+ edgeDetector: EdgeDetector,
) {
internal val scenes = SnapshotStateMap<SceneKey, Scene>()
internal val elements = SnapshotStateMap<ElementKey, Element>()
@@ -57,6 +58,7 @@
internal var onChangeScene by mutableStateOf(onChangeScene)
internal var transitions by mutableStateOf(transitions)
internal var density: Density by mutableStateOf(density)
+ internal var edgeDetector by mutableStateOf(edgeDetector)
/**
* The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 75dcb2e..b163a2a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -21,6 +21,8 @@
import androidx.compose.animation.core.snap
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
import com.android.compose.animation.scene.transformation.AnchoredSize
import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.EdgeTranslate
@@ -32,8 +34,6 @@
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.Translate
-import com.android.compose.ui.util.fastForEach
-import com.android.compose.ui.util.fastMap
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 2dc53ab..8b79c28 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -22,8 +22,6 @@
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@@ -37,6 +35,7 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.round
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -55,7 +54,7 @@
/** Whether swipe should be enabled in the given [orientation]. */
fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
- upOrLeft(orientation) != null || downOrRight(orientation) != null
+ userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
val currentScene = gestureHandler.currentScene
val canSwipe = currentScene.shouldEnableSwipes(orientation)
@@ -68,8 +67,7 @@
)
return nestedScroll(connection = gestureHandler.nestedScroll.connection)
- .draggable(
- state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta),
+ .multiPointerDraggable(
orientation = orientation,
enabled = gestureHandler.isDrivingTransition || canSwipe,
// Immediately start the drag if this our [transition] is currently animating to a scene
@@ -80,6 +78,7 @@
gestureHandler.isAnimatingOffset &&
!canOppositeSwipe,
onDragStarted = gestureHandler.draggable::onDragStarted,
+ onDragDelta = gestureHandler.draggable::onDelta,
onDragStopped = gestureHandler.draggable::onDragStopped,
)
}
@@ -159,7 +158,7 @@
internal var gestureWithPriority: Any? = null
- internal fun onDragStarted() {
+ internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
@@ -199,6 +198,48 @@
Orientation.Vertical -> layoutImpl.size.height
}.toFloat()
+ val fromEdge =
+ startedPosition?.let { position ->
+ layoutImpl.edgeDetector.edge(
+ layoutImpl.size,
+ position.round(),
+ layoutImpl.density,
+ orientation,
+ )
+ }
+
+ swipeTransition.actionUpOrLeft =
+ Swipe(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal -> SwipeDirection.Left
+ Orientation.Vertical -> SwipeDirection.Up
+ },
+ pointerCount = pointersDown,
+ fromEdge = fromEdge,
+ )
+
+ swipeTransition.actionDownOrRight =
+ Swipe(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal -> SwipeDirection.Right
+ Orientation.Vertical -> SwipeDirection.Down
+ },
+ pointerCount = pointersDown,
+ fromEdge = fromEdge,
+ )
+
+ if (fromEdge == null) {
+ swipeTransition.actionUpOrLeftNoEdge = null
+ swipeTransition.actionDownOrRightNoEdge = null
+ } else {
+ swipeTransition.actionUpOrLeftNoEdge =
+ (swipeTransition.actionUpOrLeft as Swipe).copy(fromEdge = null)
+ swipeTransition.actionDownOrRightNoEdge =
+ (swipeTransition.actionDownOrRight as Swipe).copy(fromEdge = null)
+ }
+
if (swipeTransition.absoluteDistance > 0f) {
transitionState = swipeTransition
}
@@ -246,11 +287,11 @@
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
val absoluteDistance = swipeTransition.absoluteDistance
- if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+ if (offset <= -absoluteDistance && swipeTransition.upOrLeft(fromScene) == toScene.key) {
swipeTransition.dragOffset += absoluteDistance
swipeTransition._fromScene = toScene
} else if (
- offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key
+ offset >= absoluteDistance && swipeTransition.downOrRight(fromScene) == toScene.key
) {
swipeTransition.dragOffset -= absoluteDistance
swipeTransition._fromScene = toScene
@@ -266,27 +307,21 @@
)
private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
- val maxDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
- }.toFloat()
-
- val upOrLeft = upOrLeft(orientation)
- val downOrRight = downOrRight(orientation)
+ val upOrLeft = swipeTransition.upOrLeft(this)
+ val downOrRight = swipeTransition.downOrRight(this)
// Compute the target scene depending on the current offset.
return when {
directionOffset < 0f && upOrLeft != null -> {
TargetScene(
sceneKey = upOrLeft,
- distance = -maxDistance,
+ distance = -swipeTransition.absoluteDistance,
)
}
directionOffset > 0f && downOrRight != null -> {
TargetScene(
sceneKey = downOrRight,
- distance = maxDistance,
+ distance = swipeTransition.absoluteDistance,
)
}
else -> {
@@ -516,6 +551,22 @@
var _distance by mutableFloatStateOf(0f)
val distance: Float
get() = _distance
+
+ /** The [UserAction]s associated to this swipe. */
+ var actionUpOrLeft: UserAction = Back
+ var actionDownOrRight: UserAction = Back
+ var actionUpOrLeftNoEdge: UserAction? = null
+ var actionDownOrRightNoEdge: UserAction? = null
+
+ fun upOrLeft(scene: Scene): SceneKey? {
+ return scene.userActions[actionUpOrLeft]
+ ?: actionUpOrLeftNoEdge?.let { scene.userActions[it] }
+ }
+
+ fun downOrRight(scene: Scene): SceneKey? {
+ return scene.userActions[actionDownOrRight]
+ ?: actionDownOrRightNoEdge?.let { scene.userActions[it] }
+ }
}
companion object {
@@ -526,9 +577,9 @@
private class SceneDraggableHandler(
private val gestureHandler: SceneGestureHandler,
) : DraggableHandler {
- override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) {
+ override fun onDragStarted(startedPosition: Offset, pointersDown: Int) {
gestureHandler.gestureWithPriority = this
- gestureHandler.onDragStarted()
+ gestureHandler.onDragStarted(pointersDown, startedPosition)
}
override fun onDelta(pixels: Float) {
@@ -537,7 +588,7 @@
}
}
- override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
+ override fun onDragStopped(velocity: Float) {
if (gestureHandler.gestureWithPriority == this) {
gestureHandler.gestureWithPriority = null
gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
@@ -580,11 +631,31 @@
// moving on to the next scene.
var gestureStartedOnNestedChild = false
+ val actionUpOrLeft =
+ Swipe(
+ direction =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> SwipeDirection.Left
+ Orientation.Vertical -> SwipeDirection.Up
+ },
+ pointerCount = 1,
+ )
+
+ val actionDownOrRight =
+ Swipe(
+ direction =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> SwipeDirection.Right
+ Orientation.Vertical -> SwipeDirection.Down
+ },
+ pointerCount = 1,
+ )
+
fun findNextScene(amount: Float): SceneKey? {
val fromScene = gestureHandler.currentScene
return when {
- amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation)
- amount > 0f -> fromScene.downOrRight(gestureHandler.orientation)
+ amount < 0f -> fromScene.userActions[actionUpOrLeft]
+ amount > 0f -> fromScene.userActions[actionDownOrRight]
else -> null
}
}
@@ -625,7 +696,7 @@
onStart = {
gestureHandler.gestureWithPriority = this
priorityScene = nextScene
- gestureHandler.onDragStarted()
+ gestureHandler.onDragStarted(pointersDown = 1, startedPosition = null)
},
onScroll = { offsetAvailable ->
if (gestureHandler.gestureWithPriority != this) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
deleted file mode 100644
index 741f00d..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
+++ /dev/null
@@ -1,55 +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.compose.ui.util
-
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
-/**
- * Iterates through a [List] using the index and calls [action] for each item. This does not
- * allocate an iterator like [Iterable.forEach].
- *
- * **Do not use for collections that come from public APIs**, since they may not support random
- * access in an efficient way, and this method may actually be a lot slower. Only use for
- * collections that are created by code we control and are known to support random access.
- */
-@Suppress("BanInlineOptIn")
-@OptIn(ExperimentalContracts::class)
-internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
- contract { callsInPlace(action) }
- for (index in indices) {
- val item = get(index)
- action(item)
- }
-}
-
-/**
- * Returns a list containing the results of applying the given [transform] function to each element
- * in the original collection.
- *
- * **Do not use for collections that come from public APIs**, since they may not support random
- * access in an efficient way, and this method may actually be a lot slower. Only use for
- * collections that are created by code we control and are known to support random access.
- */
-@Suppress("BanInlineOptIn")
-@OptIn(ExperimentalContracts::class)
-internal inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
- contract { callsInPlace(transform) }
- val target = ArrayList<R>(size)
- fastForEach { target += transform(it) }
- return target
-}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
new file mode 100644
index 0000000..a68282a
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FixedSizeEdgeDetectorTest {
+ private val detector = FixedSizeEdgeDetector(30.dp)
+ private val layoutSize = IntSize(100, 100)
+ private val density = Density(1f)
+
+ @Test
+ fun horizontalEdges() {
+ fun horizontalEdge(position: Int): Edge? =
+ detector.edge(
+ layoutSize,
+ position = IntOffset(position, 0),
+ density,
+ Orientation.Horizontal,
+ )
+
+ assertThat(horizontalEdge(0)).isEqualTo(Edge.Left)
+ assertThat(horizontalEdge(30)).isEqualTo(Edge.Left)
+ assertThat(horizontalEdge(31)).isEqualTo(null)
+ assertThat(horizontalEdge(69)).isEqualTo(null)
+ assertThat(horizontalEdge(70)).isEqualTo(Edge.Right)
+ assertThat(horizontalEdge(100)).isEqualTo(Edge.Right)
+ }
+
+ @Test
+ fun verticalEdges() {
+ fun verticalEdge(position: Int): Edge? =
+ detector.edge(
+ layoutSize,
+ position = IntOffset(0, position),
+ density,
+ Orientation.Vertical,
+ )
+
+ assertThat(verticalEdge(0)).isEqualTo(Edge.Top)
+ assertThat(verticalEdge(30)).isEqualTo(Edge.Top)
+ assertThat(verticalEdge(31)).isEqualTo(null)
+ assertThat(verticalEdge(69)).isEqualTo(null)
+ assertThat(verticalEdge(70)).isEqualTo(Edge.Bottom)
+ assertThat(verticalEdge(100)).isEqualTo(Edge.Bottom)
+ }
+}
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 6791a85..1eb3392 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
@@ -55,7 +55,8 @@
builder = scenesBuilder,
transitions = EmptyTestTransitions,
state = layoutState,
- density = Density(1f)
+ density = Density(1f),
+ edgeDetector = DefaultEdgeDetector,
)
.also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) },
orientation = Orientation.Vertical,
@@ -104,13 +105,13 @@
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
val transition = transitionState as Transition
@@ -123,14 +124,13 @@
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDragStopped(
- coroutineScope = coroutineScope,
velocity = velocityThreshold - 0.01f,
)
assertScene(currentScene = SceneA, isIdle = false)
@@ -142,14 +142,13 @@
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDragStopped(
- coroutineScope = coroutineScope,
velocity = velocityThreshold,
)
assertScene(currentScene = SceneC, isIdle = false)
@@ -161,23 +160,22 @@
@Test
fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
- draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f)
+ draggable.onDragStopped(velocity = 0f)
assertScene(currentScene = SceneA, isIdle = true)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDragStopped(
- coroutineScope = coroutineScope,
velocity = velocityThreshold,
)
@@ -191,7 +189,7 @@
assertScene(currentScene = SceneC, isIdle = false)
// Start a new gesture while the offset is animating
- draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ draggable.onDragStarted(startedPosition = Offset.Zero)
assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
}
@@ -320,7 +318,7 @@
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(coroutineScope, velocityThreshold)
+ draggable.onDragStopped(velocityThreshold)
assertScene(currentScene = SceneA, isIdle = true)
}
@@ -332,7 +330,7 @@
@Test
fun startNestedScrollWhileDragging() = runGestureTest {
- draggable.onDragStarted(coroutineScope, Offset.Zero)
+ draggable.onDragStarted(Offset.Zero)
assertScene(currentScene = SceneA, isIdle = false)
val transition = transitionState as Transition
@@ -344,7 +342,7 @@
assertThat(transition.progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(coroutineScope, velocityThreshold)
+ draggable.onDragStopped(velocityThreshold)
assertScene(currentScene = SceneA, isIdle = false)
nestedScrollEvents(available = offsetY10)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index df3b72a..4a6066f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -48,6 +48,14 @@
/** The middle of the layout, in pixels. */
private val Density.middle: Offset
get() = Offset((LayoutWidth / 2).toPx(), (LayoutHeight / 2).toPx())
+
+ /** The middle-top of the layout, in pixels. */
+ private val Density.middleTop: Offset
+ get() = Offset((LayoutWidth / 2).toPx(), 0f)
+
+ /** The middle-left of the layout, in pixels. */
+ private val Density.middleLeft: Offset
+ get() = Offset(0f, (LayoutHeight / 2).toPx())
}
private var currentScene by mutableStateOf(TestScenes.SceneA)
@@ -83,7 +91,13 @@
}
scene(
TestScenes.SceneC,
- userActions = mapOf(Swipe.Down to TestScenes.SceneA),
+ userActions =
+ mapOf(
+ Swipe.Down to TestScenes.SceneA,
+ Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Down, fromEdge = Edge.Top) to TestScenes.SceneB,
+ ),
) {
Box(Modifier.fillMaxSize())
}
@@ -242,4 +256,100 @@
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
}
+
+ @Test
+ fun multiPointerSwipe() {
+ // Start at scene C.
+ currentScene = TestScenes.SceneC
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ TestContent()
+ }
+
+ assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+
+ // Swipe down with two fingers.
+ rule.onRoot().performTouchInput {
+ repeat(2) { i -> down(pointerId = i, middle) }
+ repeat(2) { i ->
+ moveBy(pointerId = i, Offset(0f, touchSlop + 10.dp.toPx()), delayMillis = 1_000)
+ }
+ }
+
+ // We are transitioning to B because we used 2 fingers.
+ val transition = layoutState.transitionState
+ assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+ assertThat((transition as TransitionState.Transition).fromScene)
+ .isEqualTo(TestScenes.SceneC)
+ assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+
+ // Release the fingers and wait for the animation to end. We are back to C because we only
+ // swiped 10dp.
+ rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ }
+
+ @Test
+ fun defaultEdgeSwipe() {
+ // Start at scene C.
+ currentScene = TestScenes.SceneC
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ TestContent()
+ }
+
+ assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+
+ // Swipe down from the top edge.
+ rule.onRoot().performTouchInput {
+ down(middleTop)
+ moveBy(Offset(0f, touchSlop + 10.dp.toPx()), delayMillis = 1_000)
+ }
+
+ // We are transitioning to B (and not A) because we started from the top edge.
+ var transition = layoutState.transitionState
+ assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+ assertThat((transition as TransitionState.Transition).fromScene)
+ .isEqualTo(TestScenes.SceneC)
+ assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+
+ // Release the fingers and wait for the animation to end. We are back to C because we only
+ // swiped 10dp.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+
+ // Swipe right from the left edge.
+ rule.onRoot().performTouchInput {
+ down(middleLeft)
+ moveBy(Offset(touchSlop + 10.dp.toPx(), 0f), delayMillis = 1_000)
+ }
+
+ // We are transitioning to B (and not A) because we started from the left edge.
+ transition = layoutState.transitionState
+ assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+ assertThat((transition as TransitionState.Transition).fromScene)
+ .isEqualTo(TestScenes.SceneC)
+ assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+
+ // Release the fingers and wait for the animation to end. We are back to C because we only
+ // swiped 10dp.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ }
}
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
index 183f0e5..9d74677 100644
--- a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -15,18 +15,18 @@
~
-->
-<LinearLayout android:layout_height="match_parent"
+<RelativeLayout
android:layout_width="match_parent"
- android:orientation="vertical"
- android:layoutDirection="ltr"
- android:gravity="center"
+ android:layout_height="match_parent"
+ android:gravity="left|top"
+ android:background="@android:color/transparent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ProgressBar
android:id="@+id/side_fps_progress_bar"
- android:layout_width="55dp"
- android:layout_height="10dp"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/sfps_progress_bar_thickness"
android:indeterminateOnly="false"
android:min="0"
android:max="100"
android:progressDrawable="@drawable/progress_bar" />
-</LinearLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index d1067a9..0628c3e 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -157,4 +157,11 @@
<dimen name="weather_clock_smartspace_translateX">0dp</dimen>
<dimen name="weather_clock_smartspace_translateY">0dp</dimen>
+ <!-- Additional length to add to the SFPS sensor length we get from framework so that the length
+ of the progress bar matches the length of the power button -->
+ <dimen name="sfps_progress_bar_length_extra_padding">12dp</dimen>
+ <!-- Thickness of the progress bar we show for the SFPS based authentication. -->
+ <dimen name="sfps_progress_bar_thickness">6dp</dimen>
+ <!-- Padding from the edge of the screen for the progress bar -->
+ <dimen name="sfps_progress_bar_padding_from_edge">7dp</dimen>
</resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 9c368eb..5b59e7d 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -53,6 +53,7 @@
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
+ "tracinglib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
index 7719246..637ce5f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
@@ -19,7 +19,7 @@
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.forEach
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import java.lang.ref.WeakReference
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 631423e..10393cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -137,14 +137,12 @@
}
/**
- * @return a {@link ThumbnailData} with {@link TaskSnapshot} for the given {@param taskId}.
- * The snapshot will be triggered if no cached {@link TaskSnapshot} exists.
+ * @return the task snapshot for the given {@param taskId}.
*/
public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
TaskSnapshot snapshot = null;
try {
- snapshot = getService().getTaskSnapshot(taskId, isLowResolution,
- true /* takeSnapshotIfNeeded */);
+ snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve task snapshot", e);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.kt
deleted file mode 100644
index 7d1b65a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.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.tracing
-
-import com.android.systemui.tracing.TraceUtils.Companion.instant
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CopyableThreadContextElement
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/**
- * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-@OptIn(DelicateCoroutinesApi::class)
-@ExperimentalCoroutinesApi
-class TraceContextElement(private val traceData: TraceData = TraceData()) :
- CopyableThreadContextElement<TraceData?> {
-
- companion object Key : CoroutineContext.Key<TraceContextElement>
-
- override val key: CoroutineContext.Key<TraceContextElement> = Key
-
- @OptIn(ExperimentalStdlibApi::class)
- override fun updateThreadContext(context: CoroutineContext): TraceData? {
- val oldState = threadLocalTrace.get()
- oldState?.endAllOnThread()
- threadLocalTrace.set(traceData)
- instant("resuming ${context[CoroutineDispatcher]}")
- traceData.beginAllOnThread()
- return oldState
- }
-
- @OptIn(ExperimentalStdlibApi::class)
- override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
- instant("suspending ${context[CoroutineDispatcher]}")
- traceData.endAllOnThread()
- threadLocalTrace.set(oldState)
- oldState?.beginAllOnThread()
- }
-
- override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
- return TraceContextElement(traceData.copy())
- }
-
- override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
- return TraceContextElement(traceData.copy())
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt
deleted file mode 100644
index b68d38c..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt
+++ /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.systemui.tracing
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.tracing.TraceUtils.Companion.beginSlice
-import com.android.systemui.tracing.TraceUtils.Companion.endSlice
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-import kotlin.random.Random
-
-/**
- * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
- * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
- *
- * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
- * in does not have a [TraceContextElement].
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-val threadLocalTrace = ThreadLocal<TraceData?>()
-
-/**
- * Used for storing trace sections so that they can be added and removed from the currently running
- * thread when the coroutine is suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-class TraceData {
- private var slices = mutableListOf<TraceSection>()
-
- /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
- fun beginAllOnThread() {
- slices.forEach { beginSlice(it.name) }
- }
-
- /**
- * Removes all current trace slices from the current thread. Called when coroutine is suspended.
- */
- fun endAllOnThread() {
- for (i in 0..slices.size) {
- endSlice()
- }
- }
-
- /**
- * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
- * will also be added to the current thread immediately. This slice will not propagate to parent
- * coroutines, or to child coroutines that have already started. The unique ID is used to verify
- * that the [endSpan] is corresponds to a [beginSpan].
- */
- fun beginSpan(name: String): Int {
- val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
- slices.add(newSlice)
- beginSlice(name)
- return newSlice.id
- }
-
- /**
- * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
- * state is isolated from the parent.
- */
- fun copy(): TraceData {
- return TraceData().also { it.slices.addAll(slices) }
- }
-
- /**
- * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
- * trace slice will immediately be removed from the current thread. This information will not
- * propagate to parent coroutines, or to child coroutines that have already started.
- */
- fun endSpan(id: Int) {
- val v = slices.removeLast()
- if (v.id != id) {
- if (STRICT_MODE) {
- throw IllegalArgumentException(errorMsg)
- } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMsg)
- }
- }
- endSlice()
- }
-
- companion object {
- private const val TAG = "TraceData"
- const val INVALID_SPAN = -1
- const val FIRST_VALID_SPAN = 1
-
- /**
- * If true, throw an exception instead of printing a warning when trace sections beginnings
- * and ends are mismatched.
- */
- private val STRICT_MODE = Build.IS_ENG
-
- private const val errorMsg =
- "Mismatched trace section. This likely means you are accessing the trace local " +
- "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
- " This could happen if you are using a global dispatcher like Dispatchers.IO." +
- " To fix this, use one of the coroutine contexts provided by the dagger scope " +
- "(e.g. \"@Main CoroutineContext\")."
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.kt
deleted file mode 100644
index 469d9a2..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.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.tracing
-
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-
-/**
- * Represents a section of code executing in a coroutine. This can be split up into multiple slices
- * on different threads as the coroutine is suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @param name the name of the slice to appear on the current thread's track.
- * @param id used for matching the beginning and end of trace sections and validating correctness
- * @see traceCoroutine
- */
-data class TraceSection(
- val name: String,
- val id: Int,
-)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt
deleted file mode 100644
index 3e235f5..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt
+++ /dev/null
@@ -1,55 +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.tracing
-
-import android.os.Trace
-
-/**
- * Utility class used to log state changes easily in a track with a custom name.
- *
- * Example of usage:
- * ```kotlin
- * class MyClass {
- * val screenStateLogger = TraceStateLogger("Screen state")
- *
- * fun onTurnedOn() { screenStateLogger.log("on") }
- * fun onTurnedOff() { screenStateLogger.log("off") }
- * }
- * ```
- *
- * This creates a new slice in a perfetto trace only if the state is different than the previous
- * one.
- */
-class TraceStateLogger(
- private val trackName: String,
- private val logOnlyIfDifferent: Boolean = true,
- private val instantEvent: Boolean = true
-) {
-
- private var previousValue: String? = null
-
- /** If needed, logs the value to a track with name [trackName]. */
- fun log(newValue: String) {
- if (instantEvent) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue)
- }
- if (logOnlyIfDifferent && previousValue == newValue) return
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0)
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0)
- previousValue = newValue
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt
deleted file mode 100644
index 12a20ae..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt
+++ /dev/null
@@ -1,418 +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.tracing
-
-import android.os.Trace
-import android.os.TraceNameSupplier
-import android.util.Log
-import com.android.systemui.tracing.TraceData.Companion.FIRST_VALID_SPAN
-import com.android.systemui.tracing.TraceData.Companion.INVALID_SPAN
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.random.Random
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-/**
- * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
- * after the passed block.
- */
-inline fun <T> traceSection(tag: String, block: () -> T): T =
- if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
- Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
- try {
- block()
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_APP)
- }
- } else {
- block()
- }
-
-class TraceUtils {
- companion object {
- const val TAG = "TraceUtils"
- private const val DEBUG_COROUTINE_TRACING = false
- const val DEFAULT_TRACK_NAME = "AsyncTraces"
-
- inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
- return Runnable { traceSection(tag) { block() } }
- }
-
- /**
- * Helper function for creating a Runnable object that implements TraceNameSupplier.
- *
- * This is useful for posting Runnables to Handlers with meaningful names.
- */
- inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
- return object : Runnable, TraceNameSupplier {
- override fun getTraceName(): String = tag
- override fun run() = block()
- }
- }
-
- /**
- * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
- * there is no other way around.
- */
- val lastCookie = AtomicInteger(0)
-
- /**
- * Creates an async slice in a track called "AsyncTraces".
- *
- * This can be used to trace coroutine code. Note that all usages of this method will appear
- * under a single track.
- */
- inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync(DEFAULT_TRACK_NAME, method, block)
-
- /**
- * Creates an async slice in a track with [trackName] while [block] runs.
- *
- * This can be used to trace coroutine code. [method] will be the name of the slice,
- * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
- * SystemUI process.
- */
- inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
- val cookie = lastCookie.incrementAndGet()
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
- try {
- return block()
- } finally {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
- }
- }
-
- /**
- * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun CoroutineScope.launch(
- crossinline spanName: () -> String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> Unit
- ): Job = launch(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun CoroutineScope.launch(
- spanName: String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> Unit
- ): Job = launch(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
- * tracing
- *
- * @see traceCoroutine
- */
- inline fun <T> CoroutineScope.async(
- crossinline spanName: () -> String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> T
- ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> CoroutineScope.async(
- spanName: String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> T
- ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> runBlocking(
- crossinline spanName: () -> String,
- context: CoroutineContext,
- crossinline block: suspend () -> T
- ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> runBlocking(
- spanName: String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- suspend inline fun <T> withContext(
- spanName: String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = withContext(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- suspend inline fun <T> withContext(
- crossinline spanName: () -> String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = withContext(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
- * file. It should only every be set to true during startup. Once true, it cannot be set to
- * false again.
- */
- var coroutineTracingIsEnabled = false
- set(v) {
- if (v) field = true
- }
-
- /**
- * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
- * thread that is currently executing the [block] of work. If the [block] is suspended, all
- * trace sections added using this API will end until the [block] is resumed, which could
- * happen either on this thread or on another thread. If a child coroutine is started, it
- * will inherit the trace sections of its parent. The child will continue to print these
- * trace sections whether or not the parent coroutine is still running them.
- *
- * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
- * Otherwise, the trace sections will be dropped.
- *
- * For example, in the following trace, Thread #1 ran some work, suspended, then continued
- * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
- * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
- * Meanwhile Thread #3 is still printing trace sections from its parent because they were
- * copied when it was created. There is no way for the parent to communicate to the child
- * that it marked these slices as completed. While this might seem counterintuitive, it
- * allows us to pinpoint the origin of the child coroutine's work.
- *
- * ```
- * Thread #1 | [==== Slice A ====] [==== Slice A ====]
- * | [==== B ====] [=== B ===]
- * --------------------------------------------------------------------------------------
- * Thread #2 | [====== Slice A ======]
- * | [========= B =========]
- * | [===== C ======]
- * --------------------------------------------------------------------------------------
- * Thread #3 | [== Slice A ==] [== Slice A ==]
- * | [===== B =====] [===== B =====]
- * | [===== C =====] [===== C =====]
- * | [=== D ===]
- * ```
- *
- * @param name The name of the code section to appear in the trace
- * @see endSlice
- * @see traceCoroutine
- */
- @OptIn(ExperimentalCoroutinesApi::class)
- suspend inline fun <T> traceCoroutine(
- spanName: Lazy<String>,
- crossinline block: suspend () -> T
- ): T {
- // For coroutine tracing to work, trace spans must be added and removed even when
- // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
- // coroutine resumes when tracing is active, we won't know its name.
- val tracer = getTraceData(spanName)
- val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
-
- // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
- // of the COROUTINE_TRACING feature flag.
- val asyncTraceCookie =
- if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
- Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
- else INVALID_SPAN
- if (asyncTraceCookie != INVALID_SPAN) {
- Trace.asyncTraceForTrackBegin(
- Trace.TRACE_TAG_APP,
- DEFAULT_TRACK_NAME,
- spanName.value,
- asyncTraceCookie
- )
- }
- try {
- return block()
- } finally {
- if (asyncTraceCookie != INVALID_SPAN) {
- Trace.asyncTraceForTrackEnd(
- Trace.TRACE_TAG_APP,
- DEFAULT_TRACK_NAME,
- asyncTraceCookie
- )
- }
- tracer?.endSpan(coroutineSpanCookie)
- }
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
- if (!coroutineTracingIsEnabled) {
- logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
- } else if (coroutineContext[TraceContextElement] == null) {
- logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
- } else {
- return threadLocalTrace.get().also {
- if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
- }
- }
- return null
- }
-
- private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
- if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
- }
- }
-
- /** @see traceCoroutine */
- suspend inline fun <T> traceCoroutine(
- spanName: String,
- crossinline block: suspend () -> T
- ): T = traceCoroutine(lazyOf(spanName)) { block() }
-
- /** @see traceCoroutine */
- suspend inline fun <T> traceCoroutine(
- crossinline spanName: () -> String,
- crossinline block: suspend () -> T
- ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
-
- /**
- * Writes a trace message to indicate that a given section of code has begun running __on
- * the current thread__. This must be followed by a corresponding call to [endSlice] in a
- * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
- * idle again and starts running other, unrelated work).
- *
- * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
- * follows:
- * ```
- * Thread #1 | [==========================]
- * | [==============]
- * | [====]
- * ```
- *
- * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
- * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
- * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
- * use the term "slice" instead of "section" to be consistent with Perfetto.
- *
- * # Avoiding malformed traces
- *
- * Improper usage of this API will lead to malformed traces with long slices that sometimes
- * never end. This will look like the following:
- * ```
- * Thread #1 | [===================================================================== ...
- * | [==============] [====================================== ...
- * | [=======] [======] [===================== ...
- * | [=======]
- * ```
- *
- * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
- * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
- * technically okay to call from a suspending function if that function were to only wrap
- * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
- * calls could be mistakenly added to that block as the code is refactored.
- *
- * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
- * it with a call to [endSlice] inside that callback, even if the callback runs on the same
- * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
- * before the thread became idle and started running unrelated work.
- *
- * @param sliceName The name of the code section to appear in the trace
- * @see endSlice
- * @see traceCoroutine
- */
- fun beginSlice(sliceName: String) {
- Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
- }
-
- /**
- * Writes a trace message to indicate that a given section of code has ended. This call must
- * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
- * information regarding usage.
- *
- * @see beginSlice
- * @see traceCoroutine
- */
- fun endSlice() {
- Trace.traceEnd(Trace.TRACE_TAG_APP)
- }
-
- /**
- * Writes a trace message indicating that an instant event occurred on the current thread.
- * Unlike slices, instant events have no duration and do not need to be matched with another
- * call. Perfetto will display instant events using an arrow pointing to the timestamp they
- * occurred:
- * ```
- * Thread #1 | [==============] [======]
- * | [====] ^
- * | ^
- * ```
- *
- * @param eventName The name of the event to appear in the trace.
- */
- fun instant(eventName: String) {
- Trace.instant(Trace.TRACE_TAG_APP, eventName)
- }
-
- /**
- * Writes a trace message indicating that an instant event occurred on the given track.
- * Unlike slices, instant events have no duration and do not need to be matched with another
- * call. Perfetto will display instant events using an arrow pointing to the timestamp they
- * occurred:
- * ```
- * Async | [==============] [======]
- * Track | [====] ^
- * Name | ^
- * ```
- *
- * @param trackName The track where the event should appear in the trace.
- * @param eventName The name of the event to appear in the trace.
- */
- fun instantForTrack(trackName: String, eventName: String) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 52923a7..345f15c 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.authentication.domain.interactor
+import com.android.app.tracing.TraceUtils.Companion.async
+import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
@@ -27,8 +29,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.tracing.TraceUtils.Companion.async
-import com.android.systemui.tracing.TraceUtils.Companion.withContext
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 949c117..10e7227 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -53,6 +53,7 @@
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
import com.android.internal.annotations.VisibleForTesting
import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
@@ -63,7 +64,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.res.R
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 0567ea2..2bb19cd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -34,9 +34,11 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
@SysUISingleton
class SideFpsSensorInteractor
@@ -51,7 +53,7 @@
private val logger: SideFpsLogger,
) {
- private val sensorForCurrentDisplay =
+ private val sensorLocationForCurrentDisplay =
combine(
displayStateInteractor.displayChanges,
fingerprintPropertyRepository.sensorLocations,
@@ -77,77 +79,89 @@
isAvailable,
fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
) { sfpsAvailable, isSettingEnabled ->
- logger.logStateChange(sfpsAvailable, isSettingEnabled)
sfpsAvailable && isSettingEnabled
}
}
val sensorLocation: Flow<SideFpsSensorLocation> =
- combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
- (rotation, sensorLocation: SensorLocationInternal) ->
- val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
- // device dimensions in the current rotation
- val size = windowManager.maximumWindowMetrics.bounds
- val isDefaultOrientation = rotation.isDefaultOrientation()
- // Width and height are flipped is device is not in rotation_0 or rotation_180
- // Flipping it to the width and height of the device in default orientation.
- val displayWidth = if (isDefaultOrientation) size.width() else size.height()
- val displayHeight = if (isDefaultOrientation) size.height() else size.width()
- val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+ combine(displayStateInteractor.currentRotation, sensorLocationForCurrentDisplay, ::Pair)
+ .map { (rotation, sensorLocation: SensorLocationInternal) ->
+ val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+ // device dimensions in the current rotation
+ val windowMetrics = windowManager.maximumWindowMetrics
+ val size = windowMetrics.bounds
+ val isDefaultOrientation = rotation.isDefaultOrientation()
+ // Width and height are flipped is device is not in rotation_0 or rotation_180
+ // Flipping it to the width and height of the device in default orientation.
+ val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+ val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+ val sensorLengthInPx = sensorLocation.sensorRadius * 2
- val (sensorLeft, sensorTop) =
- if (isSensorVerticalInDefaultOrientation) {
- when (rotation) {
- DisplayRotation.ROTATION_0 -> {
- Pair(displayWidth, sensorLocation.sensorLocationY)
+ val (sensorLeft, sensorTop) =
+ if (isSensorVerticalInDefaultOrientation) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(displayWidth, sensorLocation.sensorLocationY)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(sensorLocation.sensorLocationY, 0)
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(
+ 0,
+ displayHeight -
+ sensorLocation.sensorLocationY -
+ sensorLengthInPx
+ )
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(
+ displayHeight -
+ sensorLocation.sensorLocationY -
+ sensorLengthInPx,
+ displayWidth
+ )
+ }
}
- DisplayRotation.ROTATION_90 -> {
- Pair(sensorLocation.sensorLocationY, 0)
- }
- DisplayRotation.ROTATION_180 -> {
- Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
- }
- DisplayRotation.ROTATION_270 -> {
- Pair(
- displayHeight - sensorLocation.sensorLocationY - sensorWidth,
- displayWidth
- )
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(sensorLocation.sensorLocationX, 0)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(
+ 0,
+ displayWidth - sensorLocation.sensorLocationX - sensorLengthInPx
+ )
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(
+ displayWidth -
+ sensorLocation.sensorLocationX -
+ sensorLengthInPx,
+ displayHeight
+ )
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(displayHeight, sensorLocation.sensorLocationX)
+ }
}
}
- } else {
- when (rotation) {
- DisplayRotation.ROTATION_0 -> {
- Pair(sensorLocation.sensorLocationX, 0)
- }
- DisplayRotation.ROTATION_90 -> {
- Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
- }
- DisplayRotation.ROTATION_180 -> {
- Pair(
- displayWidth - sensorLocation.sensorLocationX - sensorWidth,
- displayHeight
- )
- }
- DisplayRotation.ROTATION_270 -> {
- Pair(displayHeight, sensorLocation.sensorLocationX)
- }
- }
- }
- logger.sensorLocationStateChanged(
- size,
- rotation,
- displayWidth,
- displayHeight,
- sensorWidth,
- isSensorVerticalInDefaultOrientation
- )
-
- SideFpsSensorLocation(
- left = sensorLeft,
- top = sensorTop,
- width = sensorWidth,
- isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
- )
- }
+ SideFpsSensorLocation(
+ left = sensorLeft,
+ top = sensorTop,
+ length = sensorLengthInPx,
+ isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+ )
+ }
+ .distinctUntilChanged()
+ .onEach {
+ logger.sensorLocationStateChanged(
+ it.left,
+ it.top,
+ it.length,
+ it.isSensorVerticalInDefaultOrientation
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
index 35f8e3b..12f374f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -21,8 +21,8 @@
val left: Int,
/** Pixel offset from the top of the screen */
val top: Int,
- /** Width in pixels of the SFPS sensor */
- val width: Int,
+ /** Length of the SFPS sensor in pixels in current display density */
+ val length: Int,
/**
* Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
* Rotation_180)
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 485e512..9cab17e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,15 +1,28 @@
package com.android.systemui.communal.data.repository
import com.android.systemui.FeatureFlags
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
+
+ /**
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through
+ * [setDesiredScene].
+ */
+ val desiredScene: StateFlow<CommunalSceneKey>
+
+ /** Updates the requested scene. */
+ fun setDesiredScene(desiredScene: CommunalSceneKey)
}
@SysUISingleton
@@ -23,4 +36,12 @@
get() =
featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
featureFlags.communalHub()
+
+ private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
+ MutableStateFlow(CommunalSceneKey.Blank)
+ override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
+
+ override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+ _desiredScene.value = desiredScene
+ }
}
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 6238707..ccccbb6 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
@@ -19,10 +19,13 @@
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
/** Encapsulates business-logic related to communal mode. */
@SysUISingleton
@@ -47,4 +50,22 @@
* (have an allocated id).
*/
val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
+
+ /**
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through
+ * [onSceneChanged].
+ */
+ val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene
+
+ /**
+ * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
+ * [CommunalSceneKey.Communal].
+ */
+ val isCommunalShowing: Flow<Boolean> =
+ communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+
+ /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
+ fun onSceneChanged(newScene: CommunalSceneKey) {
+ communalRepository.setDesiredScene(newScene)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
new file mode 100644
index 0000000..2be909c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.model
+
+/** Definition of the possible scenes for the communal UI. */
+sealed class CommunalSceneKey(
+ private val loggingName: String,
+) {
+ /** The communal scene containing the hub UI. */
+ object Communal : CommunalSceneKey("communal")
+
+ /** The default scene, shows nothing and is only there to allow swiping to communal. */
+ object Blank : CommunalSceneKey("blank")
+
+ override fun toString(): String {
+ return loggingName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 390b580..de9b563 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -20,11 +20,13 @@
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@SysUISingleton
@@ -33,7 +35,7 @@
constructor(
@Application private val context: Context,
private val appWidgetHost: AppWidgetHost,
- communalInteractor: CommunalInteractor,
+ private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
) {
/** Whether communal hub should show tutorial content. */
@@ -54,4 +56,9 @@
)
}
}
+
+ val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+ fun onSceneChanged(scene: CommunalSceneKey) {
+ communalInteractor.onSceneChanged(scene)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 2ba687b..9e54b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -26,12 +26,12 @@
import android.os.Trace
import android.util.Log
import android.view.Display
+import com.android.app.tracing.traceSection
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.DisplayEvent
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.Compile
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 1c2ff4b..36d789e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -16,12 +16,14 @@
package com.android.systemui.flags;
+import static com.android.systemui.Flags.exampleFlag;
import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_NAME;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+import static com.android.systemui.shared.Flags.exampleSharedFlag;
import static java.util.Objects.requireNonNull;
@@ -539,6 +541,8 @@
pw.println("can override: true");
pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
pw.println("booleans: " + mBooleanFlagCache.size());
+ pw.println("example_flag: " + exampleFlag());
+ pw.println("example_shared_flag: " + exampleSharedFlag());
// Sort our flags for dumping
TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
dumpBooleanMap.forEach((key, value) -> pw.println(" sysui_flag_" + key + ": " + value));
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 4b98526..c490ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -30,13 +30,13 @@
import android.os.Binder
import android.os.Bundle
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.runBlocking
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
-import com.android.systemui.tracing.TraceUtils.Companion.runBlocking
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 5c1b731..f9b89b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -17,7 +17,7 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 0e795ae..c4962a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -31,7 +32,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index fbe92e3..448411e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
@@ -48,7 +49,6 @@
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.tracing.TraceUtils.Companion.withContext
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
index 1acea5c..ba4876f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -16,19 +16,27 @@
package com.android.systemui.keyguard.ui.binder
+import android.animation.ValueAnimator
+import android.graphics.Point
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.log.SideFpsLogger
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.kotlin.Quint
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
+private const val spfsProgressBarCommand = "sfps-progress-bar"
+
@SysUISingleton
class SideFpsProgressBarViewBinder
@Inject
@@ -37,38 +45,112 @@
private val view: SideFpsProgressBar,
@Application private val applicationScope: CoroutineScope,
private val sfpsController: dagger.Lazy<SideFpsController>,
+ private val logger: SideFpsLogger,
+ private val commandRegistry: CommandRegistry,
) : CoreStartable {
override fun start() {
+ commandRegistry.registerCommand(spfsProgressBarCommand) { SfpsProgressBarCommand() }
applicationScope.launch {
viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+ logger.isProlongedTouchRequiredForAuthenticationChanged(enabled)
if (enabled) {
launch {
combine(
viewModel.isVisible,
- viewModel.sensorLocation,
- viewModel.shouldRotate90Degrees,
+ viewModel.progressBarLocation,
+ viewModel.rotation,
viewModel.isFingerprintAuthRunning,
- viewModel.sensorWidth,
+ viewModel.progressBarLength,
::Quint
)
- .collectLatest {
- (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
- view.updateView(visible, location, shouldRotate, sensorWidth)
- // We have to hide the SFPS indicator as the progress bar will
- // be shown at the same location
- if (visible) {
- sfpsController.get().hideIndicator()
- } else if (fpDetectRunning) {
- sfpsController.get().showIndicator()
- }
+ .collectLatest { (visible, location, rotation, fpDetectRunning, length)
+ ->
+ updateView(
+ visible,
+ location,
+ fpDetectRunning,
+ length,
+ viewModel.progressBarThickness,
+ rotation,
+ )
}
}
launch { viewModel.progress.collectLatest { view.setProgress(it) } }
} else {
- view.hideOverlay()
+ view.hide()
}
}
}
}
+
+ private fun updateView(
+ visible: Boolean,
+ location: Point,
+ fpDetectRunning: Boolean,
+ length: Int,
+ thickness: Int,
+ rotation: Float,
+ ) {
+ logger.sfpsProgressBarStateChanged(visible, location, fpDetectRunning, length, rotation)
+ view.updateView(visible, location, length, thickness, rotation)
+ // We have to hide the SFPS indicator as the progress bar will
+ // be shown at the same location
+ if (visible) {
+ logger.hidingSfpsIndicator()
+ sfpsController.get().hideIndicator()
+ } else if (fpDetectRunning) {
+ logger.showingSfpsIndicator()
+ sfpsController.get().showIndicator()
+ }
+ }
+
+ inner class SfpsProgressBarCommand : Command {
+ private var animator: ValueAnimator? = null
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty() || args[0] == "show" && args.size != 6) {
+ pw.println("invalid command")
+ help(pw)
+ } else {
+ when (args[0]) {
+ "show" -> {
+ animator?.cancel()
+ updateView(
+ visible = true,
+ location = Point(Integer.parseInt(args[1]), Integer.parseInt(args[2])),
+ fpDetectRunning = true,
+ length = Integer.parseInt(args[3]),
+ thickness = Integer.parseInt(args[4]),
+ rotation = Integer.parseInt(args[5]).toFloat(),
+ )
+ animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+ repeatMode = ValueAnimator.REVERSE
+ repeatCount = ValueAnimator.INFINITE
+ addUpdateListener { view.setProgress(it.animatedValue as Float) }
+ }
+ animator?.start()
+ }
+ "hide" -> {
+ animator?.cancel()
+ updateView(
+ visible = false,
+ location = Point(0, 0),
+ fpDetectRunning = false,
+ length = 0,
+ thickness = 0,
+ rotation = 0.0f,
+ )
+ }
+ }
+ }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $spfsProgressBarCommand <command>")
+ pw.println("Available commands:")
+ pw.println(" show x y width height rotation")
+ pw.println(" hide")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index f7ab1ee..853f176 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -22,17 +22,16 @@
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
import android.widget.ProgressBar
-import com.android.systemui.biometrics.Utils
+import androidx.core.view.isGone
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import javax.inject.Inject
private const val TAG = "SideFpsProgressBar"
-const val progressBarHeight = 100
-
@SysUISingleton
class SideFpsProgressBar
@Inject
@@ -40,31 +39,36 @@
private val layoutInflater: LayoutInflater,
private val windowManager: WindowManager,
) {
- private var progressBarWidth = 200
+ private var overlayView: View? = null
+
fun updateView(
visible: Boolean,
- location: Point,
- shouldRotate90Degrees: Boolean,
- progressBarWidth: Int
+ viewLeftTopLocation: Point,
+ progressBarWidth: Int,
+ progressBarHeight: Int,
+ rotation: Float,
) {
if (visible) {
- this.progressBarWidth = progressBarWidth
- createAndShowOverlay(location, shouldRotate90Degrees)
+ createAndShowOverlay(viewLeftTopLocation, rotation, progressBarWidth, progressBarHeight)
} else {
- hideOverlay()
+ hide()
}
}
- fun hideOverlay() {
- overlayView = null
+ fun hide() {
+ progressBar?.isGone = true
}
private val overlayViewParams =
WindowManager.LayoutParams(
- progressBarHeight,
- progressBarWidth,
+ // overlay is always full screen
+ MATCH_PARENT,
+ MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSPARENT
)
.apply {
@@ -78,37 +82,31 @@
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
}
- private var overlayView: View? = null
- set(value) {
- field?.let { oldView -> windowManager.removeView(oldView) }
- field = value
- field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
- }
-
private fun createAndShowOverlay(
- fingerprintSensorLocation: Point,
- shouldRotate90Degrees: Boolean
+ viewLeftTop: Point,
+ rotation: Float,
+ progressBarLength: Int,
+ progressBarThickness: Int,
) {
if (overlayView == null) {
overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+ windowManager.addView(overlayView, overlayViewParams)
+ progressBar?.pivotX = 0.0f
+ progressBar?.pivotY = 0.0f
}
- overlayViewParams.x = fingerprintSensorLocation.x
- overlayViewParams.y = fingerprintSensorLocation.y
- if (shouldRotate90Degrees) {
- overlayView?.rotation = 270.0f
- overlayViewParams.width = progressBarHeight
- overlayViewParams.height = progressBarWidth
- } else {
- overlayView?.rotation = 0.0f
- overlayViewParams.width = progressBarWidth
- overlayViewParams.height = progressBarHeight
- }
- windowManager.updateViewLayout(overlayView, overlayViewParams)
+ progressBar?.layoutParams?.width = progressBarLength
+ progressBar?.layoutParams?.height = progressBarThickness
+ progressBar?.translationX = viewLeftTop.x.toFloat()
+ progressBar?.translationY = viewLeftTop.y.toFloat()
+ progressBar?.rotation = rotation
+ progressBar?.isGone = false
+ overlayView?.requestLayout()
}
fun setProgress(value: Float) {
- overlayView
- ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
- ?.setProgress((value * 100).toInt(), false)
+ progressBar?.setProgress((value * 100).toInt(), false)
}
+
+ private val progressBar: ProgressBar?
+ get() = overlayView?.findViewById(R.id.side_fps_progress_bar)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 2c3b431..f8996b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -17,10 +17,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.animation.ValueAnimator
+import android.content.Context
import android.graphics.Point
import androidx.core.animation.doOnEnd
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -36,6 +39,7 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -43,6 +47,7 @@
class SideFpsProgressBarViewModel
@Inject
constructor(
+ private val context: Context,
private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
private val sfpsSensorInteractor: SideFpsSensorInteractor,
displayStateInteractor: DisplayStateInteractor,
@@ -57,24 +62,84 @@
_progress.value = 0.0f
}
+ private val additionalSensorLengthPadding =
+ context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
+
val isVisible: Flow<Boolean> = _visible.asStateFlow()
val progress: Flow<Float> = _progress.asStateFlow()
- val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+ val progressBarLength: Flow<Int> =
+ sfpsSensorInteractor.sensorLocation
+ .map { it.length + additionalSensorLengthPadding }
+ .distinctUntilChanged()
- val sensorLocation: Flow<Point> =
- sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+ val progressBarThickness =
+ context.resources.getDimension(R.dimen.sfps_progress_bar_thickness).toInt()
+
+ val progressBarLocation =
+ combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+ .map { (rotation, sensorLocation) ->
+ val paddingFromEdge =
+ context.resources
+ .getDimension(R.dimen.sfps_progress_bar_padding_from_edge)
+ .toInt()
+ val lengthOfTheProgressBar = sensorLocation.length + additionalSensorLengthPadding
+ val viewLeftTop = Point(sensorLocation.left, sensorLocation.top)
+ val totalDistanceFromTheEdge = paddingFromEdge + progressBarThickness
+
+ val isSensorVerticalNow =
+ sensorLocation.isSensorVerticalInDefaultOrientation ==
+ rotation.isDefaultOrientation()
+ if (isSensorVerticalNow) {
+ // Sensor is vertical to the current orientation, we rotate it 270 deg
+ // around the (left,top) point as the pivot. We need to push it down the
+ // length of the progress bar so that it is still aligned to the sensor
+ viewLeftTop.y += lengthOfTheProgressBar
+ val isSensorOnTheNearEdge =
+ rotation == DisplayRotation.ROTATION_180 ||
+ rotation == DisplayRotation.ROTATION_90
+ if (isSensorOnTheNearEdge) {
+ // Add just the padding from the edge to push the progress bar right
+ viewLeftTop.x += paddingFromEdge
+ } else {
+ // View left top is pushed left from the edge by the progress bar thickness
+ // and the padding.
+ viewLeftTop.x -= totalDistanceFromTheEdge
+ }
+ } else {
+ // Sensor is horizontal to the current orientation.
+ val isSensorOnTheNearEdge =
+ rotation == DisplayRotation.ROTATION_0 ||
+ rotation == DisplayRotation.ROTATION_90
+ if (isSensorOnTheNearEdge) {
+ // Add just the padding from the edge to push the progress bar down
+ viewLeftTop.y += paddingFromEdge
+ } else {
+ // Sensor is now at the bottom edge of the device in the current rotation.
+ // We want to push it up from the bottom edge by the padding and
+ // the thickness of the progressbar.
+ viewLeftTop.y -= totalDistanceFromTheEdge
+ viewLeftTop.x -= additionalSensorLengthPadding
+ }
+ }
+ viewLeftTop
+ }
val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
- val shouldRotate90Degrees: Flow<Boolean> =
+ val rotation: Flow<Float> =
combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
.map { (rotation, sensorLocation) ->
- if (rotation.isDefaultOrientation()) {
- sensorLocation.isSensorVerticalInDefaultOrientation
+ if (
+ rotation.isDefaultOrientation() ==
+ sensorLocation.isSensorVerticalInDefaultOrientation
+ ) {
+ // We should rotate the progress bar 270 degrees in the clockwise direction with
+ // the left top point as the pivot so that it fills up from bottom to top
+ 270.0f
} else {
- !sensorLocation.isSensorVerticalInDefaultOrientation
+ 0.0f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 74923ee..919072a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -17,8 +17,6 @@
package com.android.systemui.log
import android.graphics.Point
-import android.graphics.Rect
-import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.BouncerLog
@@ -40,9 +38,9 @@
fun sfpsProgressBarStateChanged(
visible: Boolean,
location: Point,
- shouldRotate: Boolean,
fpDetectRunning: Boolean,
- sensorWidth: Int
+ sensorWidth: Int,
+ rotation: Float,
) {
buffer.log(
TAG,
@@ -51,14 +49,14 @@
bool1 = visible
int1 = location.x
int2 = location.y
- bool2 = shouldRotate
+ str1 = "$rotation"
bool3 = fpDetectRunning
long1 = sensorWidth.toLong()
},
{
"SFPS progress bar state changed: visible: $bool1, " +
"sensorLocation (x, y): ($int1, $int2), " +
- "shouldRotate = $bool2, " +
+ "rotation = $str1, " +
"fpDetectRunning: $bool3, " +
"sensorWidth: $long1"
}
@@ -87,44 +85,25 @@
)
}
- fun logStateChange(sfpsAvailable: Boolean, settingEnabled: Boolean) {
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- bool1 = sfpsAvailable
- bool2 = settingEnabled
- },
- { "SFPS rest to unlock state changed: sfpsAvailable: $bool1, settingEnabled: $bool2" }
- )
- }
-
fun sensorLocationStateChanged(
- windowSize: Rect?,
- rotation: DisplayRotation,
- displayWidth: Int,
- displayHeight: Int,
- sensorWidth: Int,
- sensorVerticalInDefaultOrientation: Boolean
+ pointOnScreenX: Int,
+ pointOnScreenY: Int,
+ sensorLength: Int,
+ isSensorVerticalInDefaultOrientation: Boolean
) {
buffer.log(
TAG,
LogLevel.DEBUG,
{
- str1 = "$windowSize"
- str2 = rotation.name
- int1 = displayWidth
- int2 = displayHeight
- long1 = sensorWidth.toLong()
- bool1 = sensorVerticalInDefaultOrientation
+ int1 = pointOnScreenX
+ int2 = pointOnScreenY
+ str2 = "$sensorLength"
+ bool1 = isSensorVerticalInDefaultOrientation
},
{
- "sensorLocation state changed: " +
- "windowSize: $str1, " +
- "rotation: $str2, " +
- "widthInRotation0: $int1, " +
- "heightInRotation0: $int2, " +
- "sensorWidth: $long1, " +
+ "SideFpsSensorLocation state changed: " +
+ "pointOnScreen: ($int1, $int2), " +
+ "sensorLength: $str2, " +
"sensorVerticalInDefaultOrientation: $bool1"
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 601aebe..3e8b49d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -56,6 +56,7 @@
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -86,7 +87,6 @@
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
-import com.android.systemui.tracing.traceSection
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index d3bc61b..a252470 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -34,6 +34,7 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.traceSection
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -66,7 +67,6 @@
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.Utils
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.animation.requiresRemeasuring
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 f3d41aa..b1ff708 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
@@ -34,8 +34,11 @@
import android.view.ViewGroupOverlay
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
import com.android.keyguard.KeyguardViewController
+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.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -53,10 +56,11 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
private val TAG: String = MediaHierarchyManager::class.java.simpleName
@@ -96,11 +100,13 @@
private val mediaManager: MediaDataManager,
private val keyguardViewController: KeyguardViewController,
private val dreamOverlayStateController: DreamOverlayStateController,
+ private val communalInteractor: CommunalInteractor,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
+ @Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
) {
@@ -209,7 +215,7 @@
else result.setIntersect(animationStartClipping, targetClipping)
}
- private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_COMMUNAL_HUB + 1)
/**
* The last location where this view was at before going to the desired location. This is useful
* for guided transitions.
@@ -401,6 +407,9 @@
}
}
+ /** Is the communal UI showing */
+ private var isCommunalShowing: Boolean = false
+
/**
* The current cross fade progress. 0.5f means it's just switching between the start and the end
* location and the content is fully faded, while 0.75f means that we're halfway faded in again
@@ -563,6 +572,14 @@
settingsObserver,
UserHandle.USER_ALL
)
+
+ // Listen to the communal UI state.
+ coroutineScope.launch {
+ communalInteractor.isCommunalShowing.collect { value ->
+ isCommunalShowing = value
+ updateDesiredLocation(forceNoAnimation = true)
+ }
+ }
}
private fun updateConfiguration() {
@@ -1115,6 +1132,9 @@
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
+ // TODO(b/308813166): revisit logic once interactions between the hub and
+ // shade/keyguard state are finalized
+ isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
@@ -1224,6 +1244,9 @@
/** Attached on the dream overlay */
const val LOCATION_DREAM_OVERLAY = 3
+ /** Attached to a view in the communal UI grid */
+ const val LOCATION_COMMUNAL_HUB = 4
+
/** Attached at the root of the hierarchy in an overlay */
const val IN_OVERLAY = -1000
@@ -1261,7 +1284,8 @@
MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS,
MediaHierarchyManager.LOCATION_LOCKSCREEN,
- MediaHierarchyManager.LOCATION_DREAM_OVERLAY
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+ MediaHierarchyManager.LOCATION_COMMUNAL_HUB
]
)
@Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
index 0129c49..1f711cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.controls.ui
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.MeasurementOutput
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 6b82746..1ec43c5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.app.tracing.traceSection
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
@@ -27,7 +28,6 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 20ea60f..16a703a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -20,11 +20,11 @@
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaLocation
+import com.android.systemui.res.R
import java.lang.IllegalArgumentException
import javax.inject.Inject
@@ -154,6 +154,8 @@
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+ MediaHierarchyManager.LOCATION_COMMUNAL_HUB ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_COMMUNAL
else -> throw IllegalArgumentException("Unknown media carousel location $location")
}
logger.log(event)
@@ -276,6 +278,8 @@
MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
@UiEvent(doc = "The media carousel moved to the dream state")
MEDIA_CAROUSEL_LOCATION_DREAM(1040),
+ @UiEvent(doc = "The media carousel moved to the communal hub UI")
+ MEDIA_CAROUSEL_LOCATION_COMMUNAL(1520),
@UiEvent(doc = "A media recommendation card was added to the media carousel")
MEDIA_RECOMMENDATION_ADDED(1041),
@UiEvent(doc = "A media recommendation card was removed from the media carousel")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 888cd0b..8f752e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -46,6 +46,7 @@
String QUICK_QS_PANEL = "media_quick_qs_panel";
String KEYGUARD = "media_keyguard";
String DREAM = "dream";
+ String COMMUNAL_HUB = "communal_Hub";
/** */
@Provides
@@ -87,6 +88,16 @@
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(COMMUNAL_HUB)
+ static MediaHost providesCommunalMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
/** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
index 97ec654..6e3b7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -31,7 +31,7 @@
private val logger: MediaMuteAwaitLogger,
@Main private val mainExecutor: Executor
) {
- private val deviceIconUtil = DeviceIconUtil()
+ private val deviceIconUtil = DeviceIconUtil(context)
/** Creates a [MediaMuteAwaitConnectionManager]. */
fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt
new file mode 100644
index 0000000..6d7d88f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileUser
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+
+interface CustomTilePackageUpdatesRepository {
+
+ val packageChanges: Flow<Unit>
+}
+
+@CustomTileBoundScope
+class CustomTilePackageUpdatesRepositoryImpl
+@Inject
+constructor(
+ tileSpec: TileSpec.CustomTileSpec,
+ @CustomTileUser user: UserHandle,
+ serviceManager: TileServiceManager,
+ defaultsRepository: CustomTileDefaultsRepository,
+ @CustomTileBoundScope boundScope: CoroutineScope,
+) : CustomTilePackageUpdatesRepository {
+
+ override val packageChanges: Flow<Unit> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ serviceManager.setTileChangeListener { changedComponentName ->
+ if (changedComponentName == tileSpec.componentName) {
+ trySend(Unit)
+ }
+ }
+
+ awaitClose { serviceManager.setTileChangeListener(null) }
+ }
+ .onEach { defaultsRepository.requestNewDefaults(user, tileSpec.componentName, true) }
+ .shareIn(boundScope, SharingStarted.WhileSubscribed())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
index e33b3e9..d382d20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
@@ -23,7 +23,7 @@
/** @see CustomTileBoundScope */
@CustomTileBoundScope
-@Subcomponent
+@Subcomponent(modules = [CustomTileBoundModule::class])
interface CustomTileBoundComponent {
@Subcomponent.Builder
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
new file mode 100644
index 0000000..889424a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.di.bound
+
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CustomTileBoundModule {
+
+ @Binds
+ fun bindCustomTilePackageUpdatesRepository(
+ impl: CustomTilePackageUpdatesRepositoryImpl
+ ): CustomTilePackageUpdatesRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1416c10..a950539 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -29,12 +29,12 @@
import android.view.RemoteAnimationTarget
import android.view.WindowManager
import android.view.WindowManagerGlobal
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 8b3548b..f56f416 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,7 +20,7 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.TraceUtils.Companion.launch
import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c6b2cf5..713ede6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.systemui.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.TraceUtils.Companion.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 385c813..38d00f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -18,9 +18,9 @@
import android.media.MediaPlayer
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.tracing.TraceUtils.Companion.async
import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ccac533..f6c25e0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,6 +5,7 @@
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
@@ -13,7 +14,6 @@
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a2627ed..a2ca49d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -113,6 +113,7 @@
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+ private final QuickSettingsController mQuickSettingsController;
private GestureDetector mPulsingWakeupGestureHandler;
private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -188,6 +189,7 @@
BouncerMessageInteractor bouncerMessageInteractor,
BouncerLogger bouncerLogger,
SysUIKeyEventHandler sysUIKeyEventHandler,
+ QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
SelectedUserInteractor selectedUserInteractor) {
@@ -220,6 +222,7 @@
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mQuickSettingsController = quickSettingsController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -454,6 +457,16 @@
&& !bouncerShowing
&& !mStatusBarStateController.isDozing()) {
if (mDragDownHelper.isDragDownEnabled()) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ // When on lockscreen, if the touch originates at the top of the screen
+ // go directly to QS and not the shade
+ if (mQuickSettingsController.shouldQuickSettingsIntercept(
+ ev.getX(), ev.getY(), 0)) {
+ mShadeLogger.d("NSWVC: QS intercepted");
+ return true;
+ }
+ }
+
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
index b2bdb72..a1a2ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -21,7 +21,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.Assert
import com.android.systemui.util.ListenerSet
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.util.Collections.unmodifiableList
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicReference
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 860697b..64970e4 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
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index e8afac5..3809ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -28,7 +28,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index 1f6f42d..1dd6242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -23,7 +23,7 @@
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.util.NamedListenerSet
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Set of classes that represent the various events that [NotifCollection] can dispatch to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index ca8e4fe..9fc4e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -23,7 +23,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.util.Compile
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index c2791a0..9b55210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index c6d8500..61e6f65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -18,7 +18,7 @@
import android.annotation.MainThread
import android.view.View
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Given a "spec" that describes a "tree" of views, adds and removes views from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 2c59ee8..1935866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 8064f04..12ee54d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,13 +32,64 @@
*/
@SysUISingleton
class ActiveNotificationListRepository @Inject constructor() {
- /**
- * Notifications actively presented to the user in the notification stack.
- *
- * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
- */
- val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+ /** Notifications actively presented to the user in the notification list. */
+ val activeNotifications = MutableStateFlow(ActiveNotificationsStore())
/** Are any already-seen notifications currently filtered out of the active list? */
val hasFilteredOutSeenNotifications = MutableStateFlow(false)
}
+
+/** Represents the notification list, comprised of groups and individual notifications. */
+data class ActiveNotificationsStore(
+ /** Notification groups, stored by key. */
+ val groups: Map<String, ActiveNotificationGroupModel> = emptyMap(),
+ /** All individual notifications, including top-level and group children, stored by key. */
+ val individuals: Map<String, ActiveNotificationModel> = emptyMap(),
+ /**
+ * Ordered top-level list of entries in the notification list (either groups or individual),
+ * represented as [Key]s. The associated [ActiveNotificationEntryModel] can be retrieved by
+ * invoking [get].
+ */
+ val renderList: List<Key> = emptyList(),
+) {
+ operator fun get(key: Key): ActiveNotificationEntryModel? {
+ return when (key) {
+ is Key.Group -> groups[key.key]
+ is Key.Individual -> individuals[key.key]
+ }
+ }
+
+ /** Unique key identifying an [ActiveNotificationEntryModel] in the store. */
+ sealed class Key {
+ data class Individual(val key: String) : Key()
+ data class Group(val key: String) : Key()
+ }
+
+ /** Mutable builder for an [ActiveNotificationsStore]. */
+ class Builder {
+ private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
+ private val individuals = mutableMapOf<String, ActiveNotificationModel>()
+ private val renderList = mutableListOf<Key>()
+
+ fun build() = ActiveNotificationsStore(groups, individuals, renderList)
+
+ fun addEntry(entry: ActiveNotificationEntryModel) {
+ when (entry) {
+ is ActiveNotificationModel -> addIndividualNotif(entry)
+ is ActiveNotificationGroupModel -> addNotifGroup(entry)
+ }
+ }
+
+ fun addIndividualNotif(notif: ActiveNotificationModel) {
+ renderList.add(Key.Individual(notif.key))
+ individuals[notif.key] = notif
+ }
+
+ fun addNotifGroup(group: ActiveNotificationGroupModel) {
+ renderList.add(Key.Group(group.key))
+ groups[group.key] = group
+ individuals[group.summary.key] = group.summary
+ group.children.forEach { individuals[it.key] = it }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index bfec60bc..85ba205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,6 +28,16 @@
repository: ActiveNotificationListRepository,
) {
/** Notifications actively presented to the user in the notification stack, in order. */
- val notifications: Flow<Collection<ActiveNotificationModel>> =
- repository.activeNotifications.map { it.values }
+ val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
+ repository.activeNotifications.map { store ->
+ store.renderList.map { key ->
+ val entry =
+ store[key]
+ ?: error("Could not find notification with key $key in active notif store.")
+ when (entry) {
+ is ActiveNotificationGroupModel -> entry.summary
+ is ActiveNotificationModel -> entry
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 604ecbc..6f4ed9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.update
-private typealias ModelStore = Map<String, ActiveNotificationModel>
-
/**
* Logic for passing information from the
* [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
@@ -37,106 +40,166 @@
private val sectionStyleProvider: SectionStyleProvider,
) {
/**
- * Sets the current list of rendered notification entries as displayed in the notification
- * stack.
- *
- * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+ * Sets the current list of rendered notification entries as displayed in the notification list.
*/
fun setRenderedList(entries: List<ListEntry>) {
repository.activeNotifications.update { existingModels ->
- entries.associateBy(
- keySelector = { it.key },
- valueTransform = { it.toModel(existingModels) },
- )
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ entries.forEach(::addListEntry)
+ }
+ }
+ }
+}
+
+private fun buildActiveNotificationsStore(
+ existingModels: ActiveNotificationsStore,
+ sectionStyleProvider: SectionStyleProvider,
+ block: ActiveNotificationsStoreBuilder.() -> Unit
+): ActiveNotificationsStore =
+ ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+
+private class ActiveNotificationsStoreBuilder(
+ private val existingModels: ActiveNotificationsStore,
+ private val sectionStyleProvider: SectionStyleProvider,
+) {
+ private val builder = ActiveNotificationsStore.Builder()
+
+ fun build(): ActiveNotificationsStore = builder.build()
+
+ /**
+ * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the
+ * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
+ * would be identical to an existing model (at the expense of additional computations).
+ */
+ fun addListEntry(entry: ListEntry) {
+ when (entry) {
+ is GroupEntry -> {
+ entry.summary?.let { summary ->
+ val summaryModel = summary.toModel()
+ val childModels = entry.children.map { it.toModel() }
+ builder.addNotifGroup(
+ existingModels.createOrReuse(
+ key = entry.key,
+ summary = summaryModel,
+ children = childModels
+ )
+ )
+ }
+ }
+ else -> {
+ entry.representativeEntry?.let { notifEntry ->
+ builder.addIndividualNotif(notifEntry.toModel())
+ }
+ }
}
}
- private fun ListEntry.toModel(
- existingModels: ModelStore,
- ): ActiveNotificationModel =
+ private fun NotificationEntry.toModel(): ActiveNotificationModel =
existingModels.createOrReuse(
key = key,
- groupKey = representativeEntry?.sbn?.groupKey,
+ groupKey = sbn.groupKey,
isAmbient = sectionStyleProvider.isMinimized(this),
- isRowDismissed = representativeEntry?.isRowDismissed == true,
+ isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
- isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
- isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
- isPulsing = representativeEntry?.showingPulsing() == true,
- aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
- shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
- statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = shouldSuppressStatusBar(),
+ isPulsing = showingPulsing(),
+ aodIcon = icons.aodIcon?.sourceIcon,
+ shelfIcon = icons.shelfIcon?.sourceIcon,
+ statusBarIcon = icons.statusBarIcon?.sourceIcon,
)
+}
- private fun ModelStore.createOrReuse(
- key: String,
- groupKey: String?,
- isAmbient: Boolean,
- isRowDismissed: Boolean,
- isSilent: Boolean,
- isLastMessageFromReply: Boolean,
- isSuppressedFromStatusBar: Boolean,
- isPulsing: Boolean,
- aodIcon: Icon?,
- shelfIcon: Icon?,
- statusBarIcon: Icon?
- ): ActiveNotificationModel {
- return this[key]?.takeIf {
- it.isCurrent(
- key = key,
- groupKey = groupKey,
- isAmbient = isAmbient,
- isRowDismissed = isRowDismissed,
- isSilent = isSilent,
- isLastMessageFromReply = isLastMessageFromReply,
- isSuppressedFromStatusBar = isSuppressedFromStatusBar,
- isPulsing = isPulsing,
- aodIcon = aodIcon,
- shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon
- )
- }
- ?: ActiveNotificationModel(
- key = key,
- groupKey = groupKey,
- isAmbient = isAmbient,
- isRowDismissed = isRowDismissed,
- isSilent = isSilent,
- isLastMessageFromReply = isLastMessageFromReply,
- isSuppressedFromStatusBar = isSuppressedFromStatusBar,
- isPulsing = isPulsing,
- aodIcon = aodIcon,
- shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon,
- )
+private fun ActiveNotificationsStore.createOrReuse(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+): ActiveNotificationModel {
+ return individuals[key]?.takeIf {
+ it.isCurrent(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon
+ )
}
+ ?: ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
+}
- private fun ActiveNotificationModel.isCurrent(
- key: String,
- groupKey: String?,
- isAmbient: Boolean,
- isRowDismissed: Boolean,
- isSilent: Boolean,
- isLastMessageFromReply: Boolean,
- isSuppressedFromStatusBar: Boolean,
- isPulsing: Boolean,
- aodIcon: Icon?,
- shelfIcon: Icon?,
- statusBarIcon: Icon?
- ): Boolean {
- return when {
- key != this.key -> false
- groupKey != this.groupKey -> false
- isAmbient != this.isAmbient -> false
- isRowDismissed != this.isRowDismissed -> false
- isSilent != this.isSilent -> false
- isLastMessageFromReply != this.isLastMessageFromReply -> false
- isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
- isPulsing != this.isPulsing -> false
- aodIcon != this.aodIcon -> false
- shelfIcon != this.shelfIcon -> false
- statusBarIcon != this.statusBarIcon -> false
- else -> true
- }
+private fun ActiveNotificationModel.isCurrent(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+): Boolean {
+ return when {
+ key != this.key -> false
+ groupKey != this.groupKey -> false
+ isAmbient != this.isAmbient -> false
+ isRowDismissed != this.isRowDismissed -> false
+ isSilent != this.isSilent -> false
+ isLastMessageFromReply != this.isLastMessageFromReply -> false
+ isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+ isPulsing != this.isPulsing -> false
+ aodIcon != this.aodIcon -> false
+ shelfIcon != this.shelfIcon -> false
+ statusBarIcon != this.statusBarIcon -> false
+ else -> true
+ }
+}
+
+private fun ActiveNotificationsStore.createOrReuse(
+ key: String,
+ summary: ActiveNotificationModel,
+ children: List<ActiveNotificationModel>,
+): ActiveNotificationGroupModel {
+ return groups[key]?.takeIf { it.isCurrent(key, summary, children) }
+ ?: ActiveNotificationGroupModel(key, summary, children)
+}
+
+private fun ActiveNotificationGroupModel.isCurrent(
+ key: String,
+ summary: ActiveNotificationModel,
+ children: List<ActiveNotificationModel>,
+): Boolean {
+ return when {
+ key != this.key -> false
+ summary != this.summary -> false
+ children != this.children -> false
+ else -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 05c88e0..9e8654a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -34,7 +34,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index 00d873e..30e2f0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -48,7 +48,7 @@
showPulsing: Boolean = true,
): Flow<Set<ActiveNotificationModel>> {
return combine(
- activeNotificationsInteractor.notifications,
+ activeNotificationsInteractor.topLevelRepresentativeNotifications,
keyguardViewStateRepository.areNotificationsFullyHidden,
) { notifications, notifsFullyHidden ->
notifications
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 53631e3..82626acc 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
@@ -61,7 +61,7 @@
darkIconInteractor.tintAreas,
darkIconInteractor.tintColor,
// Included so that tints are re-applied after entries are changed.
- notificationsInteractor.notifications,
+ notificationsInteractor.topLevelRepresentativeNotifications,
) { areas, tint, _ ->
NotificationIconColorLookup { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
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 11c9825..538be14 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.interruption
+import android.app.Notification.BubbleMetadata
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
@@ -31,6 +32,7 @@
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
import com.android.systemui.statusbar.policy.BatteryController
@@ -180,3 +182,38 @@
VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") {
override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
}
+
+class HunGroupAlertBehaviorSuppressor() :
+ VisualInterruptionFilter(
+ types = setOf(PEEK, PULSE),
+ reason = "suppressive group alert behavior"
+ ) {
+ override fun shouldSuppress(entry: NotificationEntry) =
+ entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
+}
+
+class HunJustLaunchedFsiSuppressor() :
+ VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "just launched FSI") {
+ override fun shouldSuppress(entry: NotificationEntry) = entry.hasJustLaunchedFullScreenIntent()
+}
+
+class BubbleNotAllowedSuppressor() :
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") {
+ override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
+}
+
+class BubbleNoMetadataSuppressor() :
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") {
+
+ private fun isValidMetadata(metadata: BubbleMetadata?) =
+ metadata != null && (metadata.intent != null || metadata.shortcutId != null)
+
+ override fun shouldSuppress(entry: NotificationEntry) = !isValidMetadata(entry.bubbleMetadata)
+}
+
+class AlertKeyguardVisibilitySuppressor(
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider
+) : VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "hidden on keyguard") {
+ override fun shouldSuppress(entry: NotificationEntry) =
+ keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 7f144bf..2730683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -19,6 +19,7 @@
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -41,6 +42,7 @@
private val batteryController: BatteryController,
private val globalSettings: GlobalSettings,
private val headsUpManager: HeadsUpManager,
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
private val logger: NotificationInterruptLogger,
@Main private val mainHandler: Handler,
private val powerManager: PowerManager,
@@ -65,6 +67,11 @@
addFilter(PulseEffectSuppressor())
addFilter(PulseLockscreenVisibilityPrivateSuppressor())
addFilter(PulseLowImportanceSuppressor())
+ addFilter(BubbleNotAllowedSuppressor())
+ addFilter(BubbleNoMetadataSuppressor())
+ addFilter(HunGroupAlertBehaviorSuppressor())
+ addFilter(HunJustLaunchedFsiSuppressor())
+ addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
started = true
}
@@ -100,11 +107,21 @@
condition.start()
}
+ @VisibleForTesting
+ fun removeCondition(condition: VisualInterruptionCondition) {
+ conditions.remove(condition)
+ }
+
fun addFilter(filter: VisualInterruptionFilter) {
filters.add(filter)
filter.start()
}
+ @VisibleForTesting
+ fun removeFilter(filter: VisualInterruptionFilter) {
+ filters.remove(filter)
+ }
+
override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
check(started)
return makeHeadsUpDecision(entry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index c89f2fa..f096dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -21,12 +21,12 @@
import android.util.Log
import android.util.StatsEvent
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.tracing.traceSection
import java.lang.Exception
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 78370ba..eb1c1ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,9 +17,17 @@
import android.graphics.drawable.Icon
-/** Model for entries in the notification stack. */
+/**
+ * Model for a top-level "entry" in the notification list, either an
+ * [individual notification][ActiveNotificationModel], or a [group][ActiveNotificationGroupModel].
+ */
+sealed class ActiveNotificationEntryModel
+
+/**
+ * Model for an individual notification in the notification list. These can appear as either an
+ * individual top-level notification, or as a child or summary of a [ActiveNotificationGroupModel].
+ */
data class ActiveNotificationModel(
- /** Notification key associated with this entry. */
val key: String,
/** Notification group key associated with this entry. */
val groupKey: String?,
@@ -47,4 +55,11 @@
val shelfIcon: Icon?,
/** Icon to display in the status bar. */
val statusBarIcon: Icon?,
-)
+) : ActiveNotificationEntryModel()
+
+/** Model for a group of notifications. */
+data class ActiveNotificationGroupModel(
+ val key: String,
+ val summary: ActiveNotificationModel,
+ val children: List<ActiveNotificationModel>,
+) : ActiveNotificationEntryModel()
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 a98efba..af2ca26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.reinflateAndBindLatest
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -31,7 +32,6 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 894549d..cba72d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -38,7 +38,7 @@
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.android.systemui.util.leak.RotationUtils.getExactRotation
import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.io.PrintWriter
import java.lang.Math.max
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1eea348..fb586ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.tracing.TraceUtils
+import com.android.app.tracing.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index c3c0291..2afb435 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -48,7 +48,7 @@
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.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index dfff7c4..12b8845 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -17,10 +17,10 @@
import android.content.Context
import android.os.Trace
+import com.android.app.tracing.TraceStateLogger
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.tracing.TraceStateLogger
import com.android.systemui.unfold.system.DeviceStateRepository
import com.android.systemui.unfold.updates.FoldStateRepository
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
index 1e0f420..45d742f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Consumer
diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
index cec9580..f49d616 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
@@ -20,7 +20,7 @@
import android.util.AttributeSet
import android.view.Choreographer
import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
index afd2360..de92318 100644
--- a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
@@ -13,7 +13,7 @@
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.annotation.Px
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
class DrawableSize {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 5396bca..81737c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -7,8 +7,8 @@
import com.android.systemui.dagger.qualifiers.Tracing
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
-import com.android.systemui.tracing.TraceContextElement
+import com.android.app.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.app.tracing.TraceContextElement
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
index 2e355bd..eefd849 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
@@ -18,7 +18,7 @@
import android.content.Context
import android.util.AttributeSet
import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/** LottieAnimationView that traces each call to invalidate. */
open class LottieViewWrapper : LottieAnimationView {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index 8f320a3..059ff56 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -21,7 +21,7 @@
import com.android.internal.view.RotationPolicy
import com.android.internal.view.RotationPolicy.RotationPolicyListener
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
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 728102d..0b38c4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -43,6 +43,7 @@
import android.view.animation.AccelerateInterpolator;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -67,6 +68,7 @@
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 1e7a3d3..3fbdeec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -165,7 +165,7 @@
assertThat(sensorLocation!!.left).isEqualTo(1000)
assertThat(sensorLocation!!.top).isEqualTo(200)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -193,7 +193,7 @@
assertThat(sensorLocation!!.left).isEqualTo(500)
assertThat(sensorLocation!!.top).isEqualTo(1000)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -221,7 +221,7 @@
assertThat(sensorLocation!!.left).isEqualTo(200)
assertThat(sensorLocation!!.top).isEqualTo(0)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -274,7 +274,7 @@
assertThat(sensorLocation!!.left).isEqualTo(500)
assertThat(sensorLocation!!.top).isEqualTo(0)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -301,7 +301,7 @@
assertThat(sensorLocation!!.left).isEqualTo(0)
assertThat(sensorLocation!!.top).isEqualTo(400)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -328,7 +328,7 @@
assertThat(sensorLocation!!.left).isEqualTo(400)
assertThat(sensorLocation!!.top).isEqualTo(800)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -355,7 +355,7 @@
assertThat(sensorLocation!!.left).isEqualTo(800)
assertThat(sensorLocation!!.top).isEqualTo(500)
assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
- assertThat(sensorLocation!!.width).isEqualTo(100)
+ assertThat(sensorLocation!!.length).isEqualTo(100)
}
@Test
@@ -381,10 +381,14 @@
rotation: DisplayRotation,
sensorWidth: Int
) {
- overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
setupDisplayDimensions(width, height)
currentRotation.value = rotation
- setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+ setupFingerprint(
+ x = sensorLocationX,
+ y = sensorLocationY,
+ displayId = "expanded_display",
+ sensorRadius = sensorWidth / 2
+ )
}
private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
@@ -392,7 +396,7 @@
.thenReturn(
WindowMetrics(
Rect(0, 0, displayWidth, displayHeight),
- mock(WindowInsets::class.java)
+ mock(WindowInsets::class.java),
)
)
}
@@ -401,7 +405,8 @@
fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
x: Int = 0,
y: Int = 0,
- displayId: String = "display_id_1"
+ displayId: String = "display_id_1",
+ sensorRadius: Int = 150
) {
contextDisplayInfo.uniqueId = displayId
fingerprintRepository.setProperties(
@@ -415,14 +420,14 @@
"someOtherDisplayId",
x + 100,
y + 100,
- 0,
+ sensorRadius,
),
displayId to
SensorLocationInternal(
displayId,
x,
y,
- 0,
+ sensorRadius,
)
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8e21f29..2f17b6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -86,4 +87,48 @@
val interactor = CommunalInteractor(communalRepository, widgetRepository)
assertThat(interactor.isCommunalEnabled).isFalse()
}
+
+ @Test
+ fun listensToSceneChange() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+ var desiredScene = collectLastValue(interactor.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank)
+
+ val targetScene = CommunalSceneKey.Communal
+ communalRepository.setDesiredScene(targetScene)
+ desiredScene = collectLastValue(interactor.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(targetScene)
+ }
+
+ @Test
+ fun updatesScene() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+ val targetScene = CommunalSceneKey.Communal
+
+ interactor.onSceneChanged(targetScene)
+
+ val desiredScene = collectLastValue(communalRepository.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(targetScene)
+ }
+
+ @Test
+ fun isCommunalShowing() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+
+ var isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(false)
+
+ interactor.onSceneChanged(CommunalSceneKey.Communal)
+
+ isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 5296f1a..5bfe569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -25,6 +25,10 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -46,6 +50,11 @@
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
@@ -63,6 +72,7 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -71,6 +81,7 @@
@Mock private lateinit var lockHost: MediaHost
@Mock private lateinit var qsHost: MediaHost
@Mock private lateinit var qqsHost: MediaHost
+ @Mock private lateinit var hubModeHost: MediaHost
@Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -93,10 +104,15 @@
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
+ private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
+ private val communalInteractor =
+ CommunalInteractor(communalRepository, FakeCommunalWidgetRepository())
private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
@@ -117,11 +133,13 @@
mediaDataManager,
keyguardViewController,
dreamOverlayStateController,
+ communalInteractor,
configurationController,
wakefulnessLifecycle,
notifPanelEvents,
settings,
fakeHandler,
+ testScope.backgroundScope,
ResourcesSplitShadeStateController(),
logger,
)
@@ -131,6 +149,7 @@
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
+ setupHost(hubModeHost, MediaHierarchyManager.LOCATION_COMMUNAL_HUB, COMMUNAL_TOP)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
whenever(mediaDataManager.hasActiveMedia()).thenReturn(true)
whenever(mediaCarouselController.mediaCarouselScrollHandler)
@@ -475,6 +494,33 @@
}
@Test
+ fun testCommunalLocation() =
+ testScope.runTest {
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QQS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
@@ -538,5 +584,6 @@
private const val QQS_TOP = 123
private const val QS_TOP = 456
private const val LOCKSCREEN_TOP = 789
+ private const val COMMUNAL_TOP = 111
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt
new file mode 100644
index 0000000..4a22113
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import android.content.ComponentName
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository.DefaultsRequest
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTilePackageUpdatesRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var tileServiceManager: TileServiceManager
+
+ @Captor
+ private lateinit var listenerCaptor: ArgumentCaptor<TileLifecycleManager.TileChangeListener>
+
+ private val defaultsRepository = FakeCustomTileDefaultsRepository()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var underTest: CustomTilePackageUpdatesRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CustomTilePackageUpdatesRepositoryImpl(
+ TileSpec.create(COMPONENT_1),
+ USER,
+ tileServiceManager,
+ defaultsRepository,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun packageChangesUpdatesDefaults() =
+ testScope.runTest {
+ val events = mutableListOf<Unit>()
+ underTest.packageChanges.onEach { events.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ verify(tileServiceManager).setTileChangeListener(capture(listenerCaptor))
+
+ emitPackageChange()
+ runCurrent()
+
+ assertThat(events).hasSize(1)
+ assertThat(defaultsRepository.defaultsRequests).isNotEmpty()
+ assertThat(defaultsRepository.defaultsRequests.last())
+ .isEqualTo(DefaultsRequest(USER, COMPONENT_1, true))
+ }
+
+ @Test
+ fun packageChangesEmittedOnlyForTheTile() =
+ testScope.runTest {
+ val events = mutableListOf<Unit>()
+ underTest.packageChanges.onEach { events.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ verify(tileServiceManager).setTileChangeListener(capture(listenerCaptor))
+
+ emitPackageChange(COMPONENT_2)
+ runCurrent()
+
+ assertThat(events).isEmpty()
+ }
+
+ private fun emitPackageChange(componentName: ComponentName = COMPONENT_1) {
+ listenerCaptor.value.onTileChanged(componentName)
+ }
+
+ private companion object {
+ val USER = UserHandle(0)
+ val COMPONENT_1 = ComponentName("pkg.test.1", "cls.test")
+ val COMPONENT_2 = ComponentName("pkg.test.2", "cls.test")
+ }
+}
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 4e3e165..5459779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -97,6 +97,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
@@ -111,9 +112,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -140,6 +140,7 @@
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+ @Mock private lateinit var quickSettingsController: QuickSettingsController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var lockIconViewController: LockIconViewController
@@ -166,7 +167,7 @@
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository()
private val notificationLaunchAnimationInteractor =
- NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
+ NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -274,6 +275,7 @@
),
BouncerLogger(logcatLogBuffer("BouncerLog")),
sysUIKeyEventHandler,
+ quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
@@ -460,9 +462,11 @@
// AND alternate bouncer doesn't want the touch
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
.thenReturn(false)
+ // AND quick settings controller doesn't want it
+ whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
+ .thenReturn(false)
// AND the lock icon wants the touch
- whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
- .thenReturn(true)
+ whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true)
featureFlagsClassic.set(MIGRATE_NSSL, true)
@@ -476,10 +480,31 @@
whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
// AND alternate bouncer doesn't want the touch
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
- .thenReturn(false)
+ .thenReturn(false)
// AND the lock icon does NOT want the touch
- whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
- .thenReturn(false)
+ whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
+ // AND quick settings controller doesn't want it
+ whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
+ .thenReturn(false)
+
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
+
+ // THEN touch should NOT be intercepted by NotificationShade
+ assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
+ }
+
+ @Test
+ fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() {
+ // GIVEN dozing
+ whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
+ // AND alternate bouncer doesn't want the touch
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+ .thenReturn(false)
+ // AND the lock icon does NOT want the touch
+ whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
+ // AND quick settings controller DOES want it
+ whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
+ .thenReturn(true)
featureFlagsClassic.set(MIGRATE_NSSL, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 3d5d26a..a6ab6a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -121,6 +121,7 @@
@Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var quickSettingsController: QuickSettingsController
@Mock
private lateinit var notificationStackScrollLayoutController:
NotificationStackScrollLayoutController
@@ -264,6 +265,7 @@
),
BouncerLogger(logcatLogBuffer("BouncerLog")),
Mockito.mock(SysUIKeyEventHandler::class.java),
+ quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index ca8ea4e..b86f841 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.byKey
import com.android.systemui.util.mockito.mock
@@ -40,9 +41,19 @@
@Test
fun setRenderedList_preservesOrdering() = runTest {
- val notifs by collectLastValue(notifsInteractor.notifications)
+ val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
val keys = (1..50).shuffled().map { "$it" }
- val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+ val entries =
+ keys.map {
+ mock<ListEntry> {
+ val mockRep = mock<NotificationEntry> {
+ whenever(key).thenReturn(it)
+ whenever(sbn).thenReturn(mock())
+ whenever(icons).thenReturn(mock())
+ }
+ whenever(representativeEntry).thenReturn(mockRep)
+ }
+ }
underTest.setRenderedList(entries)
assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index ec80e5f..f8252a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.notification.shared.byIsAmbient
@@ -77,7 +78,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
@@ -196,7 +199,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
@@ -318,7 +323,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index ba68fbb..44acac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.phone.DozeParameters
@@ -342,14 +343,17 @@
val icon: Icon = mock()
shadeRepository.setLegacyShadeExpansion(0f)
activeNotificationsRepository.activeNotifications.value =
- listOf(
- activeNotificationModel(
- key = "notif1",
- groupKey = "group",
- statusBarIcon = icon
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
)
- )
- .associateBy { it.key }
+ }
+ .build()
val isolatedIcon by collectLastValue(underTest.isolatedIcon)
runCurrent()
@@ -368,14 +372,17 @@
val icon: Icon = mock()
shadeRepository.setLegacyShadeExpansion(.5f)
activeNotificationsRepository.activeNotifications.value =
- listOf(
- activeNotificationModel(
- key = "notif1",
- groupKey = "group",
- statusBarIcon = icon
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
)
- )
- .associateBy { it.key }
+ }
+ .build()
val isolatedIcon by collectLastValue(underTest.isolatedIcon)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index ff89bdb..80d941a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -18,6 +18,11 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@@ -29,6 +34,7 @@
batteryController,
globalSettings,
headsUpManager,
+ keyguardNotificationVisibilityProvider,
logger,
mainHandler,
powerManager,
@@ -37,4 +43,179 @@
userTracker,
)
}
+
+ @Test
+ fun testNothingCondition_suppressesNothing() {
+ withCondition(TestCondition(types = emptySet()) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testNothingFilter_suppressesNothing() {
+ withFilter(TestFilter(types = emptySet()) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testPeekCondition_suppressesOnlyPeek() {
+ withCondition(TestCondition(types = setOf(PEEK)) { true }) {
+ assertPeekSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testPeekFilter_suppressesOnlyPeek() {
+ withFilter(TestFilter(types = setOf(PEEK)) { true }) {
+ assertPeekSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testPulseCondition_suppressesOnlyPulse() {
+ withCondition(TestCondition(types = setOf(PULSE)) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testPulseFilter_suppressesOnlyPulse() {
+ withFilter(TestFilter(types = setOf(PULSE)) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseSuppressed()
+ assertBubbleNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testBubbleCondition_suppressesOnlyBubble() {
+ withCondition(TestCondition(types = setOf(BUBBLE)) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleSuppressed()
+ }
+ }
+
+ @Test
+ fun testBubbleFilter_suppressesOnlyBubble() {
+ withFilter(TestFilter(types = setOf(BUBBLE)) { true }) {
+ assertPeekNotSuppressed()
+ assertPulseNotSuppressed()
+ assertBubbleSuppressed()
+ }
+ }
+
+ @Test
+ fun testCondition_differentState() {
+ ensurePeekState()
+ val entry = buildPeekEntry()
+
+ var stateShouldSuppress = false
+ withCondition(TestCondition(types = setOf(PEEK)) { stateShouldSuppress }) {
+ assertShouldHeadsUp(entry)
+
+ stateShouldSuppress = true
+ assertShouldNotHeadsUp(entry)
+
+ stateShouldSuppress = false
+ assertShouldHeadsUp(entry)
+ }
+ }
+
+ @Test
+ fun testFilter_differentState() {
+ ensurePeekState()
+ val entry = buildPeekEntry()
+
+ var stateShouldSuppress = false
+ withFilter(TestFilter(types = setOf(PEEK)) { stateShouldSuppress }) {
+ assertShouldHeadsUp(entry)
+
+ stateShouldSuppress = true
+ assertShouldNotHeadsUp(entry)
+
+ stateShouldSuppress = false
+ assertShouldHeadsUp(entry)
+ }
+ }
+
+ @Test
+ fun testFilter_differentNotif() {
+ ensurePeekState()
+
+ val suppressedEntry = buildPeekEntry()
+ val unsuppressedEntry = buildPeekEntry()
+
+ withFilter(TestFilter(types = setOf(PEEK)) { it == suppressedEntry }) {
+ assertShouldNotHeadsUp(suppressedEntry)
+ assertShouldHeadsUp(unsuppressedEntry)
+ }
+ }
+
+ private fun assertPeekSuppressed() {
+ ensurePeekState()
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ private fun assertPeekNotSuppressed() {
+ ensurePeekState()
+ assertShouldHeadsUp(buildPeekEntry())
+ }
+
+ private fun assertPulseSuppressed() {
+ ensurePulseState()
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+
+ private fun assertPulseNotSuppressed() {
+ ensurePulseState()
+ assertShouldHeadsUp(buildPulseEntry())
+ }
+
+ private fun assertBubbleSuppressed() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry())
+ }
+
+ private fun assertBubbleNotSuppressed() {
+ ensureBubbleState()
+ assertShouldBubble(buildBubbleEntry())
+ }
+
+ private fun withCondition(condition: VisualInterruptionCondition, block: () -> Unit) {
+ provider.addCondition(condition)
+ block()
+ provider.removeCondition(condition)
+ }
+
+ private fun withFilter(filter: VisualInterruptionFilter, block: () -> Unit) {
+ provider.addFilter(filter)
+ block()
+ provider.removeFilter(filter)
+ }
+
+ private class TestCondition(
+ types: Set<VisualInterruptionType>,
+ val onShouldSuppress: () -> Boolean
+ ) : VisualInterruptionCondition(types = types, reason = "") {
+ override fun shouldSuppress(): Boolean = onShouldSuppress()
+ }
+
+ private class TestFilter(
+ types: Set<VisualInterruptionType>,
+ val onShouldSuppress: (NotificationEntry) -> Boolean = { true }
+ ) : VisualInterruptionFilter(types = types, reason = "") {
+ override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index df12289..7f12b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -20,6 +20,9 @@
import android.app.Notification
import android.app.Notification.BubbleMetadata
import android.app.Notification.FLAG_BUBBLE
+import android.app.Notification.GROUP_ALERT_ALL
+import android.app.Notification.GROUP_ALERT_CHILDREN
+import android.app.Notification.GROUP_ALERT_SUMMARY
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
@@ -305,10 +308,132 @@
assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
}
+ private fun withPeekAndPulseEntry(
+ extendEntry: EntryBuilder.() -> Unit,
+ block: (NotificationEntry) -> Unit
+ ) {
+ ensurePeekState()
+ block(buildPeekEntry(extendEntry))
+
+ ensurePulseState()
+ block(buildPulseEntry(extendEntry))
+ }
+
@Test
- fun testShouldBubble() {
+ fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = true
+ groupAlertBehavior = GROUP_ALERT_ALL
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = true
+ groupAlertBehavior = GROUP_ALERT_SUMMARY
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = true
+ groupAlertBehavior = GROUP_ALERT_CHILDREN
+ }) {
+ assertShouldNotHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() {
+ withPeekAndPulseEntry({
+ isGrouped = false
+ isGroupSummary = true
+ groupAlertBehavior = GROUP_ALERT_CHILDREN
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_ALL
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_CHILDREN
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_SUMMARY
+ }) {
+ assertShouldNotHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() {
+ withPeekAndPulseEntry({
+ isGrouped = false
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_SUMMARY
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldNotHeadsUp_justLaunchedFsi() {
+ withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { assertShouldNotHeadsUp(it) }
+ }
+
+ @Test
+ fun testShouldBubble_withIntentAndIcon() {
ensureBubbleState()
- assertShouldBubble(buildBubbleEntry())
+ assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false })
+ }
+
+ @Test
+ fun testShouldBubble_withShortcut() {
+ ensureBubbleState()
+ assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true })
+ }
+
+ @Test
+ fun testShouldNotBubble_notAllowed() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+ }
+
+ @Test
+ fun testShouldNotBubble_noBubbleMetadata() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
}
@Test
@@ -340,6 +465,18 @@
}
@Test
+ fun testShouldNotAlert_hiddenOnKeyguard() {
+ ensurePeekState({ keyguardShouldHideNotification = true })
+ assertShouldNotHeadsUp(buildPeekEntry())
+
+ ensurePulseState({ keyguardShouldHideNotification = true })
+ assertShouldNotHeadsUp(buildPulseEntry())
+
+ ensureBubbleState({ keyguardShouldHideNotification = true })
+ assertShouldNotBubble(buildBubbleEntry())
+ }
+
+ @Test
fun testShouldFsi_notInteractive() {
ensureNotInteractiveFsiState()
assertShouldFsi(buildFsiEntry())
@@ -357,7 +494,7 @@
assertShouldFsi(buildFsiEntry())
}
- private data class State(
+ protected data class State(
var hunSettingEnabled: Boolean? = null,
var hunSnoozed: Boolean? = null,
var isAodPowerSave: Boolean? = null,
@@ -370,7 +507,7 @@
var statusBarState: Int? = null,
)
- private fun setState(state: State): Unit =
+ protected fun setState(state: State): Unit =
state.run {
hunSettingEnabled?.let {
val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
@@ -401,7 +538,7 @@
statusBarState?.let { statusBarStateController.state = it }
}
- private fun ensureState(block: State.() -> Unit) =
+ protected fun ensureState(block: State.() -> Unit) =
State()
.apply {
keyguardShouldHideNotification = false
@@ -409,7 +546,7 @@
}
.run(this::setState)
- private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
+ protected fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
hunSettingEnabled = true
hunSnoozed = false
isDozing = false
@@ -418,67 +555,67 @@
run(block)
}
- private fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
+ protected fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
isAodPowerSave = false
isDozing = true
pulseOnNotificationsEnabled = true
run(block)
}
- private fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
+ protected fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
- private fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
+ protected fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
isDreaming = false
isInteractive = false
statusBarState = SHADE
run(block)
}
- private fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
+ protected fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
isDreaming = true
isInteractive = true
statusBarState = SHADE
run(block)
}
- private fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
+ protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
isDreaming = false
isInteractive = true
statusBarState = KEYGUARD
run(block)
}
- private fun assertShouldHeadsUp(entry: NotificationEntry) =
+ protected fun assertShouldHeadsUp(entry: NotificationEntry) =
provider.makeUnloggedHeadsUpDecision(entry).let {
assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
}
- private fun assertShouldNotHeadsUp(entry: NotificationEntry) =
+ protected fun assertShouldNotHeadsUp(entry: NotificationEntry) =
provider.makeUnloggedHeadsUpDecision(entry).let {
assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
}
- private fun assertShouldBubble(entry: NotificationEntry) =
+ protected fun assertShouldBubble(entry: NotificationEntry) =
provider.makeAndLogBubbleDecision(entry).let {
assertTrue("unexpected suppressed bubble: ${it.logReason}", it.shouldInterrupt)
}
- private fun assertShouldNotBubble(entry: NotificationEntry) =
+ protected fun assertShouldNotBubble(entry: NotificationEntry) =
provider.makeAndLogBubbleDecision(entry).let {
assertFalse("unexpected unsuppressed bubble: ${it.logReason}", it.shouldInterrupt)
}
- private fun assertShouldFsi(entry: NotificationEntry) =
+ protected fun assertShouldFsi(entry: NotificationEntry) =
provider.makeUnloggedFullScreenIntentDecision(entry).let {
assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
}
- private fun assertShouldNotFsi(entry: NotificationEntry) =
+ protected fun assertShouldNotFsi(entry: NotificationEntry) =
provider.makeUnloggedFullScreenIntentDecision(entry).let {
assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
}
- private class EntryBuilder(val context: Context) {
+ protected class EntryBuilder(val context: Context) {
var importance = IMPORTANCE_DEFAULT
var suppressedVisualEffects: Int? = null
var whenMs: Long? = null
@@ -487,20 +624,33 @@
var canBubble: Boolean? = null
var isBubble = false
var hasBubbleMetadata = false
- var bubbleSuppressNotification: Boolean? = null
+ var bubbleIsShortcut = false
+ var bubbleSuppressesNotification: Boolean? = null
+ var isGrouped = false
+ var isGroupSummary: Boolean? = null
+ var groupAlertBehavior: Int? = null
+ var hasJustLaunchedFsi = false
- private fun buildBubbleMetadata() =
- BubbleMetadata.Builder(
- PendingIntent.getActivity(
- context,
- /* requestCode = */ 0,
- Intent().setPackage(context.packageName),
- FLAG_MUTABLE
- ),
- Icon.createWithResource(context.resources, R.drawable.android)
- )
- .apply { bubbleSuppressNotification?.let { setSuppressNotification(it) } }
- .build()
+ private fun buildBubbleMetadata(): BubbleMetadata {
+ val builder =
+ if (bubbleIsShortcut) {
+ BubbleMetadata.Builder(context.packageName + ":test_shortcut_id")
+ } else {
+ BubbleMetadata.Builder(
+ PendingIntent.getActivity(
+ context,
+ /* requestCode = */ 0,
+ Intent().setPackage(context.packageName),
+ FLAG_MUTABLE
+ ),
+ Icon.createWithResource(context.resources, R.drawable.android)
+ )
+ }
+
+ bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) }
+
+ return builder.build()
+ }
fun build() =
Notification.Builder(context, TEST_CHANNEL_ID)
@@ -517,6 +667,14 @@
if (hasBubbleMetadata) {
setBubbleMetadata(buildBubbleMetadata())
}
+
+ if (isGrouped) {
+ setGroup(TEST_GROUP_KEY)
+ }
+
+ isGroupSummary?.let { setGroupSummary(it) }
+
+ groupAlertBehavior?.let { setGroupAlertBehavior(it) }
}
.build()
.apply {
@@ -537,6 +695,10 @@
}
.build()!!
.also {
+ if (hasJustLaunchedFsi) {
+ it.notifyFullScreenIntentLaunched()
+ }
+
modifyRanking(it)
.apply {
suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
@@ -546,27 +708,27 @@
}
}
- private fun buildEntry(block: EntryBuilder.() -> Unit) =
+ protected fun buildEntry(block: EntryBuilder.() -> Unit) =
EntryBuilder(context).also(block).build()
- private fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ protected fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
importance = IMPORTANCE_HIGH
run(block)
}
- private fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ protected fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
importance = IMPORTANCE_DEFAULT
visibilityOverride = VISIBILITY_NO_OVERRIDE
run(block)
}
- private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
canBubble = true
hasBubbleMetadata = true
run(block)
}
- private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ protected fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
importance = IMPORTANCE_HIGH
hasFsi = true
run(block)
@@ -581,3 +743,4 @@
private const val TEST_CHANNEL_NAME = "Test Channel"
private const val TEST_PACKAGE = "test_package"
private const val TEST_TAG = "test_tag"
+private const val TEST_GROUP_KEY = "test_group_key"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
index 8fb5ff8..ba34ce6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
@@ -12,7 +12,7 @@
* permissions and limitations under the License.
*/
-package com.android.systemui.tracing
+package com.android.app.tracing
import android.os.Handler
import android.os.Looper
@@ -76,7 +76,7 @@
@Test
fun testLongTraceSection_doesNotThrow_whenUsingHelper() {
traceSection(SECTION_NAME_THATS_TOO_LONG) {
- Log.v(TAG, "com.android.systemui.tracing.traceSection() block.")
+ Log.v(TAG, "com.android.app.tracing.traceSection() block.")
}
}
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 e1c6dde..799bb40 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
@@ -1,8 +1,17 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import kotlinx.coroutines.flow.MutableStateFlow
+
/** Fake implementation of [CommunalRepository]. */
-class FakeCommunalRepository : CommunalRepository {
- override var isCommunalEnabled = false
+class FakeCommunalRepository(
+ override var isCommunalEnabled: Boolean = false,
+ override val desiredScene: MutableStateFlow<CommunalSceneKey> =
+ MutableStateFlow(CommunalSceneKey.Blank)
+) : CommunalRepository {
+ override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+ this.desiredScene.value = desiredScene
+ }
fun setIsCommunalEnabled(value: Boolean) {
isCommunalEnabled = value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTilePackageUpdatesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTilePackageUpdatesRepository.kt
new file mode 100644
index 0000000..8f972f5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTilePackageUpdatesRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+class FakeCustomTilePackageUpdatesRepository : CustomTilePackageUpdatesRepository {
+
+ private val mutablePackageChanges = MutableSharedFlow<Unit>()
+
+ override val packageChanges: Flow<Unit>
+ get() = mutablePackageChanges
+
+ suspend fun emitPackageChange() {
+ mutablePackageChanges.emit(Unit)
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index cfe2af9..5953d0d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -39,9 +39,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
+import com.android.server.accessibility.gestures.GestureMatcher;
import com.android.server.accessibility.gestures.MultiTap;
import com.android.server.accessibility.gestures.MultiTapAndHold;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -453,20 +456,45 @@
private final MagnificationGesturesObserver mGesturesObserver;
DetectingState(@UiContext Context context) {
- final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1,
- mDetectSingleFingerTripleTap
- ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP
- : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null);
- final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context,
- mDetectSingleFingerTripleTap ? 3 : 1,
- mDetectSingleFingerTripleTap
- ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD
- : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null);
- mGesturesObserver = new MagnificationGesturesObserver(this,
- new SimpleSwipe(context),
- multiTap,
- multiTapAndHold,
- new TwoFingersDownOrSwipe(context));
+ if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+ final List<GestureMatcher> mGestureMatchers = new ArrayList<>();
+
+ mGestureMatchers.add(new SimpleSwipe(context));
+ // Observe single tap and single tap and hold to reduce response time when the
+ // user performs these two gestures inside the window magnifier.
+ mGestureMatchers.add(new MultiTap(context,
+ mDetectSingleFingerTripleTap ? 3 : 1,
+ mDetectSingleFingerTripleTap
+ ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP
+ : MagnificationGestureMatcher.GESTURE_SINGLE_TAP,
+ null));
+ mGestureMatchers.add(new MultiTapAndHold(context,
+ mDetectSingleFingerTripleTap ? 3 : 1,
+ mDetectSingleFingerTripleTap
+ ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD
+ : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD,
+ null));
+ mGestureMatchers.add(new TwoFingersDownOrSwipe(context));
+
+ mGesturesObserver = new MagnificationGesturesObserver(this,
+ mGestureMatchers.toArray(new GestureMatcher[mGestureMatchers.size()]));
+ } else {
+ final MultiTap multiTap = new MultiTap(context,
+ mDetectSingleFingerTripleTap ? 3 : 1,
+ mDetectSingleFingerTripleTap
+ ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP
+ : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null);
+ final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context,
+ mDetectSingleFingerTripleTap ? 3 : 1,
+ mDetectSingleFingerTripleTap
+ ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD
+ : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null);
+ mGesturesObserver = new MagnificationGesturesObserver(this,
+ new SimpleSwipe(context),
+ multiTap,
+ multiTapAndHold,
+ new TwoFingersDownOrSwipe(context));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 88bb66f..9677248 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1886,7 +1886,7 @@
int exitInfoReason = (int) args.arg3;
args.recycle();
forceStopPackageLocked(pkg, appId, false, false, true, false,
- false, userId, reason, exitInfoReason);
+ false, false, userId, reason, exitInfoReason);
}
} break;
@@ -3914,7 +3914,10 @@
+ packageName + ": " + e);
}
if (mUserController.isUserRunning(user, userRunningFlags)) {
- forceStopPackageLocked(packageName, pkgUid,
+ forceStopPackageLocked(packageName, UserHandle.getAppId(pkgUid),
+ false /* callerWillRestart */, false /* purgeCache */,
+ true /* doIt */, false /* evenPersistent */,
+ false /* uninstalling */, true /* packageStateStopped */, user,
reason == null ? ("from pid " + callingPid) : reason);
finishForceStopPackageLocked(packageName, pkgUid);
}
@@ -4163,7 +4166,7 @@
@GuardedBy("this")
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
- false, true, false, false, UserHandle.getUserId(uid), reason);
+ false, true, false, false, false, UserHandle.getUserId(uid), reason);
}
@GuardedBy("this")
@@ -4349,20 +4352,20 @@
@GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
- boolean evenPersistent, boolean uninstalling, int userId, String reasonString) {
-
+ boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
+ int userId, String reasonString) {
int reason = packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
: ApplicationExitInfo.REASON_USER_REQUESTED;
return forceStopPackageLocked(packageName, appId, callerWillRestart, purgeCache, doit,
- evenPersistent, uninstalling, userId, reasonString, reason);
+ evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason);
}
@GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
- boolean evenPersistent, boolean uninstalling, int userId, String reasonString,
- int reason) {
+ boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
+ int userId, String reasonString, int reason) {
int i;
if (userId == UserHandle.USER_ALL && packageName == null) {
@@ -4443,7 +4446,7 @@
}
}
- if (packageName == null || uninstalling) {
+ if (packageName == null || uninstalling || packageStateStopped) {
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
packageName, userId, appId, doit);
}
@@ -5148,7 +5151,7 @@
for (String pkg : pkgs) {
synchronized (ActivityManagerService.this) {
if (forceStopPackageLocked(pkg, -1, false, false, false, false, false,
- 0, "query restart")) {
+ false, 0, "query restart")) {
setResultCode(Activity.RESULT_OK);
return;
}
@@ -7342,7 +7345,7 @@
mDebugTransient = !persistent;
if (packageName != null) {
forceStopPackageLocked(packageName, -1, false, false, true, true,
- false, UserHandle.USER_ALL, "set debug app");
+ false, false, UserHandle.USER_ALL, "set debug app");
}
}
} finally {
@@ -14918,7 +14921,7 @@
if (list != null && list.length > 0) {
for (int i = 0; i < list.length; i++) {
forceStopPackageLocked(list[i], -1, false, true, true,
- false, false, userId, "storage unmount");
+ false, false, false, userId, "storage unmount");
}
mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
sendPackageBroadcastLocked(
@@ -14945,8 +14948,8 @@
if (killProcess) {
forceStopPackageLocked(ssp, UserHandle.getAppId(
intent.getIntExtra(Intent.EXTRA_UID, -1)),
- false, true, true, false, fullUninstall, userId,
- "pkg removed");
+ false, true, true, false, fullUninstall, false,
+ userId, "pkg removed");
getPackageManagerInternal()
.onPackageProcessKilledForUninstall(ssp);
} else {
@@ -15864,7 +15867,7 @@
} else {
// Instrumentation can kill and relaunch even persistent processes
forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false,
- userId, "start instr");
+ false, userId, "start instr");
// Inform usage stats to make the target package active
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(ii.targetPackage, userId,
@@ -15993,6 +15996,7 @@
/* doIt= */ true,
/* evenPersistent= */ true,
/* uninstalling= */ false,
+ /* packageStateStopped= */ false,
userId,
"start instr");
@@ -16163,8 +16167,7 @@
}
} else if (!instr.mNoRestart) {
forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false,
- app.userId,
- "finished inst");
+ false, app.userId, "finished inst");
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7c079702..2efac12 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2040,7 +2040,7 @@
// the package was initially frozen through KILL_APPLICATION_MSG, so
// it doesn't hurt to use it again.)
mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
- false, false, true, false, false, app.userId, "start failure");
+ false, false, true, false, false, false, app.userId, "start failure");
return false;
}
}
@@ -2115,7 +2115,7 @@
+ app.processName, e);
app.setPendingStart(false);
mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
- false, false, true, false, false, app.userId, "start failure");
+ false, false, true, false, false, false, app.userId, "start failure");
}
return app.getPid() > 0;
}
@@ -2148,7 +2148,7 @@
app.setPendingStart(false);
mService.forceStopPackageLocked(app.info.packageName,
UserHandle.getAppId(app.uid),
- false, false, true, false, false, app.userId, "start failure");
+ false, false, true, false, false, false, app.userId, "start failure");
}
}
};
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c5dd01f..ae62a7a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3629,7 +3629,7 @@
void activityManagerForceStopPackage(@UserIdInt int userId, String reason) {
synchronized (mService) {
- mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+ mService.forceStopPackageLocked(null, -1, false, false, true, false, false, false,
userId, reason);
}
};
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4538cad..0629e637 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -39,6 +39,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IAuthService;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
@@ -333,6 +334,33 @@
}
@Override
+ public long getLastAuthenticationTime(int userId,
+ @Authenticators.Types int authenticators) throws RemoteException {
+ // Only allow internal clients to call getLastAuthenticationTime with a different
+ // userId.
+ final int callingUserId = UserHandle.getCallingUserId();
+
+ if (userId != callingUserId) {
+ checkInternalPermission();
+ } else {
+ checkPermission();
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // We can't do this above because we need the READ_DEVICE_CONFIG permission, which
+ // the calling user may not possess.
+ if (!Flags.lastAuthenticationTime()) {
+ throw new UnsupportedOperationException();
+ }
+
+ return mBiometricService.getLastAuthenticationTime(userId, authenticators);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public boolean hasEnrolledBiometrics(int userId, String opPackageName)
throws RemoteException {
checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1898b80..91a68ea 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -20,6 +20,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -39,6 +40,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -53,6 +55,7 @@
import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -62,10 +65,16 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.security.Authorization;
+import android.security.GateKeeper;
import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.security.authorization.ResponseCode;
+import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
@@ -79,6 +88,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -116,6 +126,10 @@
KeyStore mKeyStore;
@VisibleForTesting
ITrustManager mTrustManager;
+ @VisibleForTesting
+ IKeystoreAuthorization mKeystoreAuthorization;
+ @VisibleForTesting
+ IGateKeeperService mGateKeeper;
// Get and cache the available biometric authenticators and their associated info.
final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
@@ -616,6 +630,64 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
+ public long getLastAuthenticationTime(
+ int userId, @Authenticators.Types int authenticators) {
+ super.getLastAuthenticationTime_enforcePermission();
+
+ if (!Flags.lastAuthenticationTime()) {
+ throw new UnsupportedOperationException();
+ }
+
+ Slogf.d(TAG, "getLastAuthenticationTime(userId=%d, authenticators=0x%x)",
+ userId, authenticators);
+
+ final long secureUserId;
+ try {
+ secureUserId = mGateKeeper.getSecureUserId(userId);
+ } catch (RemoteException e) {
+ Slogf.w(TAG, "Failed to get secure user id for " + userId, e);
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+
+ if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
+ Slogf.w(TAG, "No secure user id for " + userId);
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+
+ ArrayList<Integer> hardwareAuthenticators = new ArrayList<>(2);
+
+ if ((authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
+ hardwareAuthenticators.add(HardwareAuthenticatorType.PASSWORD);
+ }
+
+ if ((authenticators & Authenticators.BIOMETRIC_STRONG) != 0) {
+ hardwareAuthenticators.add(HardwareAuthenticatorType.FINGERPRINT);
+ }
+
+ if (hardwareAuthenticators.isEmpty()) {
+ throw new IllegalArgumentException("authenticators must not be empty");
+ }
+
+ int[] authTypesArray = hardwareAuthenticators.stream()
+ .mapToInt(Integer::intValue)
+ .toArray();
+ try {
+ return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error getting last auth time: " + e);
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ } catch (ServiceSpecificException e) {
+ // This is returned when the feature flag test fails in keystore2
+ if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+ throw new UnsupportedOperationException();
+ }
+
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
@@ -937,6 +1009,14 @@
return ActivityManager.getService();
}
+ public IKeystoreAuthorization getKeystoreAuthorizationService() {
+ return Authorization.getService();
+ }
+
+ public IGateKeeperService getGateKeeperService() {
+ return GateKeeper.getService();
+ }
+
public ITrustManager getTrustManager() {
return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
}
@@ -1050,6 +1130,8 @@
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
mBiometricCameraManager = injector.getBiometricCameraManager(context);
+ mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+ mGateKeeper = injector.getGateKeeperService();
try {
injector.getActivityManagerService().registerUserSwitchObserver(
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e475fe6..ff12ca2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -102,6 +102,7 @@
import android.media.projection.IMediaProjectionManager;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -239,6 +240,10 @@
private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
+
+ @VisibleForTesting
+ static final String ENABLE_ON_CONNECT =
+ "persist.sys.display.enable_on_connect.external";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
// This value needs to be in sync with the threshold
// in RefreshRateConfigs::getFrameRateDivisor.
@@ -1530,8 +1535,8 @@
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
- + "display. In order to create a virtual display that does not perform"
- + "screen sharing (mirroring), please use the flag"
+ + "display. In order to create a virtual display that does not perform "
+ + "screen sharing (mirroring), please use the flag "
+ "VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY.");
}
}
@@ -1942,10 +1947,14 @@
}
setupLogicalDisplay(display);
-
// TODO(b/292196201) Remove when the display can be disabled before DPC is created.
if (display.getDisplayInfoLocked().type == Display.TYPE_EXTERNAL) {
- display.setEnabledLocked(false);
+ if ((Build.IS_ENG || Build.IS_USERDEBUG)
+ && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
+ Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
+ } else {
+ display.setEnabledLocked(false);
+ }
}
sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2951ef6..a52870e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -112,6 +112,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
@@ -159,6 +160,7 @@
import com.android.server.EventLogTags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemConfig;
+import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -2524,8 +2526,15 @@
LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.FilteredSnapshot snapshot =
packageManagerLocal.withFilteredSnapshot()) {
- DexoptParams params =
- dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+ boolean ignoreDexoptProfile =
+ (installRequest.getInstallFlags()
+ & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
+ != 0;
+ /*@DexoptFlags*/ int extraFlags =
+ ignoreDexoptProfile && Flags.useArtServiceV2()
+ ? ArtFlags.FLAG_IGNORE_PROFILE
+ : 0;
+ DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage(
snapshot, packageName, params);
installRequest.onDexoptFinished(dexOptResult);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 305e353..a4d8632 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -58,6 +58,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
@@ -666,17 +667,22 @@
// App package name and label length is restricted so that really long strings aren't
// written to disk.
- if (params.appPackageName != null
- && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ if (params.appPackageName != null && !isValidPackageName(params.appPackageName)) {
params.appPackageName = null;
}
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
- String requestedInstallerPackageName = (params.installerPackageName != null
- && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
- ? params.installerPackageName : installerPackageName;
+ // Validate installer package name.
+ if (params.installerPackageName != null && !isValidPackageName(
+ params.installerPackageName)) {
+ params.installerPackageName = null;
+ }
+
+ var requestedInstallerPackageName =
+ params.installerPackageName != null ? params.installerPackageName
+ : installerPackageName;
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| PackageInstallerSession.isSystemDataLoaderInstallation(params)
@@ -1105,6 +1111,19 @@
return Integer.parseInt(sessionId);
}
+ private static boolean isValidPackageName(@NonNull String packageName) {
+ if (packageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ return false;
+ }
+ // "android" is a valid package name
+ var errorMessage = FrameworkParsingPackageUtils.validateName(
+ packageName, /* requireSeparator= */ false, /* requireFilename */ true);
+ if (errorMessage != null) {
+ return false;
+ }
+ return true;
+ }
+
private File getTmpSessionDir(String volumeUuid) {
return Environment.getDataAppDirectory(volumeUuid);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7264e2e..d4abad8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3703,6 +3703,9 @@
sessionParams.installFlags |=
PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
break;
+ case "--ignore-dexopt-profile":
+ sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE;
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4799,7 +4802,7 @@
pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--non-staged] [--force-non-staged]");
- pw.println(" [--staged-ready-timeout TIMEOUT]");
+ pw.println(" [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
pw.println(" [PATH [SPLIT...]|-]");
pw.println(" Install an application. Must provide the apk data to install, either as");
pw.println(" file path(s) or '-' to read from stdin. Options are:");
@@ -4839,6 +4842,13 @@
pw.println(" milliseconds for pre-reboot verification to complete when");
pw.println(" performing staged install. This flag is used to alter the waiting");
pw.println(" time. You can skip the waiting time by specifying a TIMEOUT of '0'");
+ pw.println(" --ignore-dexopt-profile: If set, all profiles are ignored by dexopt");
+ pw.println(" during the installation, including the profile in the DM file and");
+ pw.println(" the profile embedded in the APK file. If an invalid profile is");
+ pw.println(" provided during installation, no warning will be reported by `adb");
+ pw.println(" install`.");
+ pw.println(" This option does not affect later dexopt operations (e.g.,");
+ pw.println(" background dexopt and manual `pm compile` invocations).");
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
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 2ad8bcf..9e20805 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -379,10 +379,7 @@
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
- if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0
- && state.isQuarantined()) {
- ai.enabled = false;
- } else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
ai.enabled = true;
} else if (state.getEnabledState()
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c021785..f462efc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -69,6 +69,7 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3825,8 +3826,7 @@
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution,
- boolean takeSnapshotIfNeeded) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -3840,12 +3840,8 @@
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- TaskSnapshot taskSnapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+ return mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
task.mUserId, true /* restoreFromDisk */, isLowResolution);
- if (taskSnapshot == null && takeSnapshotIfNeeded) {
- taskSnapshot = takeTaskSnapshot(taskId, false /* updateCache */);
- }
- return taskSnapshot;
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -7064,8 +7060,7 @@
@Override
public TaskSnapshot getTaskSnapshotBlocking(
int taskId, boolean isLowResolution) {
- return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution,
- false /* takeSnapshotIfNeeded */);
+ return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
}
@Override
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9f3e162..668cd87 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -28,6 +28,7 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.window.flags.Flags.balShowToasts;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -459,6 +460,7 @@
"With Android 15 BAL hardening this activity start would be blocked"
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
+ showBalToast("BAL would be blocked", state);
// return the realCaller result for backwards compatibility
return statsLog(resultForRealCaller, state);
}
@@ -470,6 +472,7 @@
"With Android 15 BAL hardening this activity start would be blocked"
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
+ showBalToast("BAL would be blocked", state);
return statsLog(resultForCaller, state);
}
if (resultForRealCaller.allows()
@@ -481,6 +484,7 @@
"With Android 14 BAL hardening this activity start would be blocked"
+ " (missing opt in by PI sender)! "
+ state.dump(resultForCaller, resultForRealCaller));
+ showBalToast("BAL would be blocked", state);
return statsLog(resultForRealCaller, state);
}
Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -488,6 +492,7 @@
+ state.dump(resultForCaller, resultForRealCaller));
// fall through
}
+ showBalToast("BAL blocked", state);
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity launch blocked"
+ state.dump(resultForCaller, resultForRealCaller));
@@ -862,8 +867,7 @@
+ (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+ getApplicationLabel(mService.mContext.getPackageManager(),
launchedFromPackageName);
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- toastText, Toast.LENGTH_LONG).show());
+ showToast(toastText);
Slog.i(TAG, asmDebugInfo);
}
@@ -882,6 +886,19 @@
return true;
}
+ private void showBalToast(String toastText, BalState state) {
+ if (balShowToasts()) {
+ showToast(toastText
+ + " caller:" + state.mCallingPackage
+ + " realCaller:" + state.mRealCallingPackage);
+ }
+ }
+
+ private void showToast(String toastText) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ toastText, Toast.LENGTH_LONG).show());
+ }
+
/**
* If the top activity uid does not match the launching or launched activity, and the launch was
* not requested from the top uid, we want to clear out all non matching activities to prevent
@@ -930,12 +947,10 @@
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
&& (!shouldBlockActivityStart || finishCount[0] > 0)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (shouldBlockActivityStart
- ? "Top activities cleared by "
- : "Top activities would be cleared by ")
- + ActivitySecurityModelFeatureFlags.DOC_LINK,
- Toast.LENGTH_LONG).show());
+ showToast((shouldBlockActivityStart
+ ? "Top activities cleared by "
+ : "Top activities would be cleared by ")
+ + ActivitySecurityModelFeatureFlags.DOC_LINK);
Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
@@ -1013,11 +1028,10 @@
}
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (ActivitySecurityModelFeatureFlags.DOC_LINK
- + (restrictActivitySwitch ? " returned home due to "
- : " would return home due to ")
- + callingLabel), Toast.LENGTH_LONG).show());
+ showToast((ActivitySecurityModelFeatureFlags.DOC_LINK
+ + (restrictActivitySwitch ? " returned home due to "
+ : " would return home due to ")
+ + callingLabel));
}
// If the activity switch should be restricted, return home rather than the
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5c5a1e1..f348928 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3508,10 +3508,13 @@
top.mLetterboxUiController.getLetterboxPositionForVerticalReachability();
}
}
- // User Aspect Ratio Settings is enabled if the app is not in SCM
+ // User Aspect Ratio Settings button is enabled if the app is not in SCM and has
+ // launchable activities
info.topActivityEligibleForUserAspectRatioButton = top != null
&& !info.topActivityInSizeCompat
- && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings();
+ && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings()
+ && mAtmService.mContext.getPackageManager()
+ .getLaunchIntentForPackage(getBasePackageName()) != null;
info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
}
diff --git a/services/midi/OWNERS b/services/midi/OWNERS
index f4d51f9..683cae1 100644
--- a/services/midi/OWNERS
+++ b/services/midi/OWNERS
@@ -1 +1,3 @@
philburk@google.com
+robertwu@google.com
+elaurent@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 163d248..c7b1abf 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -28,6 +28,8 @@
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.server.display.DisplayManagerService.ENABLE_ON_CONNECT;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
import static com.google.common.truth.Truth.assertThat;
@@ -90,12 +92,14 @@
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -113,6 +117,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -130,6 +135,7 @@
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -141,6 +147,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.time.Duration;
import java.util.ArrayList;
@@ -322,6 +330,12 @@
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@Mock DisplayManagerFlags mMockFlags;
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .build();
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -2406,6 +2420,39 @@
}
@Test
+ public void testConnectExternalDisplay_withDisplayManagementAndSysprop_shouldEnableDisplay() {
+ Assume.assumeTrue(Build.IS_ENG || Build.IS_USERDEBUG);
+ when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ doAnswer((Answer<Boolean>) invocationOnMock -> true)
+ .when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
+ manageDisplaysPermission(/* granted= */ true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ localService.registerDisplayGroupListener(callback);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+
+ // Create default display device
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
+ callback.clear();
+
+ callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ callback.waitForExpectedEvent();
+
+ LogicalDisplay display =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ false);
+ assertThat(display.isEnabledLocked()).isTrue();
+ assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
+ EVENT_DISPLAY_CONNECTED, EVENT_DISPLAY_ADDED).inOrder();
+ }
+
+ @Test
public void testConnectInternalDisplay_withDisplayManagement_shouldConnectAndAddDisplay() {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/idle/DeviceIdlenessTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/idle/DeviceIdlenessTrackerTest.java
new file mode 100644
index 0000000..09935f2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/idle/DeviceIdlenessTrackerTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.job.controllers.idle;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.controllers.idle.DeviceIdlenessTracker.KEY_INACTIVITY_IDLE_THRESHOLD_MS;
+import static com.android.server.job.controllers.idle.DeviceIdlenessTracker.KEY_INACTIVITY_STABLE_POWER_IDLE_THRESHOLD_MS;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppSchedulingModuleThread;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceIdlenessTrackerTest {
+ private DeviceIdlenessTracker mDeviceIdlenessTracker;
+ private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
+ private BroadcastReceiver mBroadcastReceiver;
+ private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+ // Called in DeviceIdlenessTracker.startTracking.
+ when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
+ when(mContext.getResources()).thenReturn(mResources);
+ doReturn((int) (31 * MINUTE_IN_MILLIS)).when(mResources).getInteger(
+ com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
+ doReturn((int) (17 * MINUTE_IN_MILLIS)).when(mResources).getInteger(
+ com.android.internal.R.integer
+ .config_jobSchedulerInactivityIdleThresholdOnStablePower);
+ doReturn(mPowerManager).when(() -> LocalServices.getService(PowerManager.class));
+
+ // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+ // in the past, and QuotaController sometimes floors values at 0, so if the test time
+ // causes sessions with negative timestamps, they will fail.
+ JobSchedulerService.sSystemClock =
+ getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
+ JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
+ Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
+ JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mDeviceIdlenessTracker = new DeviceIdlenessTracker();
+ mDeviceIdlenessTracker.startTracking(mContext,
+ mJobSchedulerService, mock(IdlenessListener.class));
+
+ verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private Clock getAdvancedClock(Clock clock, long incrementMs) {
+ return Clock.offset(clock, Duration.ofMillis(incrementMs));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+ JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+ }
+
+ private void setBatteryState(boolean isCharging, boolean isBatteryNotLow) {
+ doReturn(isCharging).when(mJobSchedulerService).isBatteryCharging();
+ doReturn(isBatteryNotLow).when(mJobSchedulerService).isBatteryNotLow();
+ mDeviceIdlenessTracker.onBatteryStateChanged(isCharging, isBatteryNotLow);
+ }
+
+ private void setDeviceConfigLong(String key, long val) {
+ mDeviceConfigPropertiesBuilder.setLong(key, val);
+ mDeviceIdlenessTracker.processConstant(mDeviceConfigPropertiesBuilder.build(), key);
+ }
+
+ @Test
+ public void testThresholdChangeWithStablePowerChange() {
+ setDeviceConfigLong(KEY_INACTIVITY_IDLE_THRESHOLD_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(KEY_INACTIVITY_STABLE_POWER_IDLE_THRESHOLD_MS, 5 * MINUTE_IN_MILLIS);
+ setBatteryState(false, false);
+
+ Intent screenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
+ mBroadcastReceiver.onReceive(mContext, screenOffIntent);
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ long expectedUnstableAlarmElapsed = nowElapsed + 10 * MINUTE_IN_MILLIS;
+ long expectedStableAlarmElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
+
+ InOrder inOrder = inOrder(mAlarmManager);
+ inOrder.verify(mAlarmManager)
+ .setWindow(anyInt(), eq(expectedUnstableAlarmElapsed), anyLong(), anyString(),
+ eq(AppSchedulingModuleThread.getExecutor()), any());
+
+ // Advanced the clock a little to make sure the tracker continues to use the original time.
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+
+ // Charging isn't enough for stable power.
+ setBatteryState(true, false);
+ inOrder.verify(mAlarmManager, never())
+ .setWindow(anyInt(), anyLong(), anyLong(), anyString(),
+ eq(AppSchedulingModuleThread.getExecutor()), any());
+
+ // Now on stable power.
+ setBatteryState(true, true);
+ inOrder.verify(mAlarmManager)
+ .setWindow(anyInt(), eq(expectedStableAlarmElapsed), anyLong(), anyString(),
+ eq(AppSchedulingModuleThread.getExecutor()), any());
+
+ // Battery-not-low isn't enough for stable power. Go back to unstable timing.
+ setBatteryState(false, true);
+ inOrder.verify(mAlarmManager)
+ .setWindow(anyInt(), eq(expectedUnstableAlarmElapsed), anyLong(), anyString(),
+ eq(AppSchedulingModuleThread.getExecutor()), any());
+
+ // Still not on stable power.
+ setBatteryState(false, false);
+ inOrder.verify(mAlarmManager, never())
+ .setWindow(anyInt(), anyLong(), anyLong(), anyString(),
+ eq(AppSchedulingModuleThread.getExecutor()), any());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index f88afe7..a78f2dc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -41,6 +41,8 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -53,6 +55,7 @@
import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -84,6 +87,8 @@
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -418,6 +423,37 @@
eq(callback));
}
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetLastAuthenticationTime_flaggedOff_throwsUnsupportedOperationException()
+ throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ mAuthService.mImpl.getLastAuthenticationTime(0,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void testGetLastAuthenticationTime_flaggedOn_callsBiometricService()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final int userId = 0;
+ final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+ mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+
+ waitForIdle();
+ verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+ }
+
private static void setInternalAndTestBiometricPermissions(
Context context, boolean hasPermission) {
for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 0230d77..408442b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -71,12 +72,17 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.security.GateKeeper;
import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.service.gatekeeper.IGateKeeperService;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -92,6 +98,7 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
@@ -105,6 +112,9 @@
@SmallTest
public class BiometricServiceTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String TEST_PACKAGE_NAME = "test_package";
private static final long TEST_REQUEST_ID = 44;
@@ -162,10 +172,16 @@
@Mock
private BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ private IKeystoreAuthorization mKeystoreAuthService;
+
+ @Mock
+ private IGateKeeperService mGateKeeperService;
+
BiometricContextProvider mBiometricContextProvider;
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
resetReceivers();
@@ -215,6 +231,9 @@
mStatusBarService, null /* handler */,
mAuthSessionCoordinator);
when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
+ when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+ when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
+ when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
@@ -1751,6 +1770,44 @@
verifyNoMoreInteractions(callback);
}
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperationException()
+ throws RemoteException {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void testGetLastAuthenticationTime_flagOn_callsKeystoreAuthorization()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+ final int[] hardwareAuthenticators = new int[] {
+ HardwareAuthenticatorType.PASSWORD,
+ HardwareAuthenticatorType.FINGERPRINT
+ };
+
+ final int userId = 0;
+ final long secureUserId = mGateKeeperService.getSecureUserId(userId);
+
+ assertNotEquals(GateKeeper.INVALID_SECURE_USER_ID, secureUserId);
+
+ final long expectedResult = 31337L;
+
+ when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+ .thenReturn(expectedResult);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+
+ final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
+ Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
+
+ assertEquals(expectedResult, result);
+ verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
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 461d637..2598a6b 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
@@ -347,6 +347,8 @@
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = UNIQUE_ID;
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock)
+ .getDisplayIdToMirror(anyInt());
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
@@ -1627,6 +1629,7 @@
@Test
public void openNonBlockedAppOnMirrorDisplay_flagDisabled_launchesActivity() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
when(mDisplayManagerInternalMock.getDisplayIdToMirror(anyInt()))
.thenReturn(Display.DEFAULT_DISPLAY);
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
index af633cc..dbd6c88 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
@@ -37,6 +37,7 @@
import android.os.Binder;
import android.testing.TestableContext;
import android.util.ArraySet;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -137,6 +138,8 @@
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = "uniqueId";
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock)
+ .getDisplayIdToMirror(anyInt());
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 4c25a4b..3b4b220 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1444,7 +1444,7 @@
});
assertSecurityException(expectCallable,
() -> mAtm.startActivityFromRecents(0, new Bundle()));
- assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true, false));
+ assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true));
assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
assertSecurityException(expectCallable,
() -> mAtm.unregisterTaskStackListener(null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 435a835..0639deb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -73,6 +73,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -570,12 +571,15 @@
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
final Task task = rootTask.getBottomMostTask();
final ActivityRecord root = task.getTopNonFinishingActivity();
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
spyOn(mWm.mLetterboxConfiguration);
spyOn(root);
spyOn(root.mLetterboxUiController);
doReturn(true).when(root.mLetterboxUiController)
.shouldEnableUserAspectRatioSettings();
+ doReturn(new Intent()).when(pm).getLaunchIntentForPackage(anyString());
doReturn(false).when(root).inSizeCompatMode();
doReturn(task).when(root).getOrganizedTask();
@@ -593,6 +597,10 @@
doReturn(true).when(root).inSizeCompatMode();
assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
doReturn(false).when(root).inSizeCompatMode();
+
+ // When app doesn't have any launchable activities the button is not enabled
+ doReturn(null).when(pm).getLaunchIntentForPackage(anyString());
+ assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e413663..f64ab22 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -57,6 +57,7 @@
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageEventsQuery;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
@@ -113,6 +114,8 @@
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import com.android.server.utils.AlarmQueue;
+import libcore.util.EmptyArray;
+
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
@@ -1478,6 +1481,14 @@
* Called by the Binder stub.
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
+ return queryEventsWithTypes(userId, beginTime, endTime, flags, EmptyArray.INT);
+ }
+
+ /**
+ * Called by the Binder stub.
+ */
+ UsageEvents queryEventsWithTypes(int userId, long beginTime, long endTime, int flags,
+ int[] eventTypeFilter) {
synchronized (mLock) {
if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1488,7 +1499,7 @@
if (service == null) {
return null; // user was stopped or removed
}
- return service.queryEvents(beginTime, endTime, flags);
+ return service.queryEvents(beginTime, endTime, flags, eventTypeFilter);
}
}
@@ -2123,7 +2134,7 @@
private final class BinderService extends IUsageStatsManager.Stub {
- private boolean hasPermission(String callingPackage) {
+ private boolean hasQueryPermission(String callingPackage) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
return true;
@@ -2203,10 +2214,37 @@
return uid == Process.SYSTEM_UID;
}
+ private UsageEvents queryEventsHelper(int userId, long beginTime, long endTime,
+ String callingPackage, int[] eventTypeFilter) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
+ callingUid, userId);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
+ userId, callingPackage, callingPid, callingUid);
+ final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+ final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+ callingPid, callingUid);
+ int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+ if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+ if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+ if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+ if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+
+ return UsageStatsService.this.queryEventsWithTypes(userId, beginTime, endTime,
+ flags, eventTypeFilter);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
long endTime, String callingPackage, int userId) {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
@@ -2234,7 +2272,7 @@
@Override
public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType,
long beginTime, long endTime, String callingPackage) throws RemoteException {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
@@ -2256,7 +2294,7 @@
@Override
public ParceledListSlice<EventStats> queryEventStats(int bucketType,
long beginTime, long endTime, String callingPackage) throws RemoteException {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
@@ -2277,32 +2315,25 @@
@Override
public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
- final int userId = UserHandle.getCallingUserId();
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
- callingUid, userId);
+ return queryEventsHelper(UserHandle.getCallingUserId(), beginTime, endTime,
+ callingPackage, /* eventTypeFilter= */ EmptyArray.INT);
+ }
- final long token = Binder.clearCallingIdentity();
- try {
- final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
- userId, callingPackage, callingPid, callingUid);
- final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
- callingPid, callingUid);
- int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
- if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
- if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
- if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
- if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
- } finally {
- Binder.restoreCallingIdentity(token);
+ @Override
+ public UsageEvents queryEventsWithFilter(@NonNull UsageEventsQuery query,
+ @NonNull String callingPackage) {
+ Objects.requireNonNull(query);
+ Objects.requireNonNull(callingPackage);
+
+ if (!hasQueryPermission(callingPackage)) {
+ return null;
}
+ return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
+ query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
}
@Override
@@ -2312,7 +2343,7 @@
final int callingUserId = UserHandle.getUserId(callingUid);
checkCallerIsSameApp(callingPackage);
- final boolean includeTaskRoot = hasPermission(callingPackage);
+ final boolean includeTaskRoot = hasQueryPermission(callingPackage);
final long token = Binder.clearCallingIdentity();
try {
@@ -2326,7 +2357,7 @@
@Override
public UsageEvents queryEventsForUser(long beginTime, long endTime, int userId,
String callingPackage) {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
@@ -2337,33 +2368,14 @@
"No permission to query usage stats for this user");
}
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
- callingUid, callingUserId);
-
- final long token = Binder.clearCallingIdentity();
- try {
- final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
- userId, callingPackage, callingPid, callingUid);
- final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
- callingPid, callingUid);
- boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
- if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
- if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
- if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
- if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return queryEventsHelper(userId, beginTime, endTime, callingPackage,
+ /* eventTypeFilter= */ EmptyArray.INT);
}
@Override
public UsageEvents queryEventsForPackageForUser(long beginTime, long endTime,
int userId, String pkg, String callingPackage) {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
return null;
}
if (userId != UserHandle.getCallingUserId()) {
@@ -2404,7 +2416,7 @@
if (actualCallingUid != callingUid) {
return false;
}
- } else if (!hasPermission(callingPackage)) {
+ } else if (!hasQueryPermission(callingPackage)) {
return false;
}
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
@@ -2454,7 +2466,7 @@
final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
// If the calling app is asking about itself, continue, else check for permission.
final boolean sameApp = packageUid == callingUid;
- if (!sameApp && !hasPermission(callingPackage)) {
+ if (!sameApp && !hasQueryPermission(callingPackage)) {
throw new SecurityException("Don't have permission to query app standby bucket");
}
@@ -2502,7 +2514,7 @@
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
- if (!hasPermission(callingPackageName)) {
+ if (!hasQueryPermission(callingPackageName)) {
throw new SecurityException(
"Don't have permission to query app standby bucket");
}
@@ -2556,7 +2568,7 @@
final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
// If the calling app is asking about itself, continue, else check for permission.
if (packageUid != callingUid) {
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
throw new SecurityException(
"Don't have permission to query min app standby bucket");
}
@@ -2900,7 +2912,7 @@
if (!hasPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
throw new SecurityException("Caller doesn't have INTERACT_ACROSS_USERS permission");
}
- if (!hasPermission(callingPackage)) {
+ if (!hasQueryPermission(callingPackage)) {
throw new SecurityException("Don't have permission to query usage stats");
}
synchronized (mLock) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index ddb2796..9b67ab6 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -70,7 +70,7 @@
* in UsageStatsService.
*/
class UserUsageStatsService {
- private static final String TAG = "UsageStatsService";
+ private static final String TAG = UsageStatsService.TAG;
private static final boolean DEBUG = UsageStatsService.DEBUG;
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final int sDateFormatFlags =
@@ -535,10 +535,23 @@
return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner, true);
}
- UsageEvents queryEvents(final long beginTime, final long endTime, int flags) {
+ UsageEvents queryEvents(final long beginTime, final long endTime, int flags,
+ int[] eventTypeFilter) {
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
return null;
}
+
+ // Ensure valid event type filter.
+ final boolean isQueryForAllEvents = ArrayUtils.isEmpty(eventTypeFilter);
+ final boolean[] queryEventFilter = new boolean[Event.MAX_EVENT_TYPE + 1];
+ if (!isQueryForAllEvents) {
+ for (int eventType : eventTypeFilter) {
+ if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
+ throw new IllegalArgumentException("invalid event type: " + eventType);
+ }
+ queryEventFilter[eventType] = true;
+ }
+ }
final ArraySet<String> names = new ArraySet<>();
List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, new StatCombiner<Event>() {
@@ -547,6 +560,7 @@
List<Event> accumulatedResult) {
final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
final int size = stats.events.size();
+
for (int i = startIndex; i < size; i++) {
Event event = stats.events.get(i);
if (event.mTimeStamp >= endTime) {
@@ -554,6 +568,10 @@
}
final int eventType = event.mEventType;
+ if (!isQueryForAllEvents && !queryEventFilter[eventType]) {
+ continue;
+ }
+
if (eventType == Event.SHORTCUT_INVOCATION
&& (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
continue;
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 85709c9..ed71531 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -28,7 +28,13 @@
<option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
<!-- b/307664397 - Ensure camera has the correct permissions and doesn't show a dialog -->
<option name="run-command"
+ value="pm grant com.google.android.GoogleCamera android.permission.CAMERA"/>
+ <option name="run-command"
+ value="pm grant com.google.android.GoogleCamera android.permission.RECORD_AUDIO"/>
+ <option name="run-command"
value="pm grant com.google.android.GoogleCamera android.permission.ACCESS_FINE_LOCATION"/>
+ <option name="run-command"
+ value="pm grant com.google.android.GoogleCamera android.permission.ACCESS_COARSE_LOCATION"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index cb37821..59dc689 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -313,7 +313,8 @@
@Test
public void testBuilderAndGettersSafeModeDisabled() {
- final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+ final VcnGatewayConnectionConfig config =
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertFalse(config.isSafeModeEnabled());
}
@@ -335,7 +336,8 @@
@Test
public void testPersistableBundleSafeModeDisabled() {
- final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+ final VcnGatewayConnectionConfig config =
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
}
@@ -456,7 +458,7 @@
assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled());
final VcnGatewayConnectionConfig configNotEqual =
- newBuilderMinimal().enableSafeMode(false).build();
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertEquals(config, configEqual);
assertNotEquals(config, configNotEqual);
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index bf73198..f846164 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -661,7 +661,7 @@
throws Exception {
final VcnGatewayConnectionConfig config =
VcnGatewayConnectionConfigTest.newTestBuilderMinimal()
- .enableSafeMode(safeModeEnabledByCaller)
+ .setSafeModeEnabled(safeModeEnabledByCaller)
.build();
final VcnGatewayConnection.Dependencies deps =
mock(VcnGatewayConnection.Dependencies.class);