Merge "Remove the scaling code" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 98b62b3..0ee7ace 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,7 +36,9 @@
":android.location.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.platform.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.wifi.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
@@ -60,13 +62,13 @@
":android.webkit.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
+ ":backup_flags_lib{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
- ":com.android.net.flags-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
@@ -108,7 +110,9 @@
"android.media.midi-aconfig",
"android.media.tv.flags-aconfig",
"android.multiuser.flags-aconfig",
+ "android.net.platform.flags-aconfig",
"android.net.vcn.flags-aconfig",
+ "android.net.wifi.flags-aconfig",
"android.nfc.flags-aconfig",
"android.os.flags-aconfig",
"android.os.vibrator.flags-aconfig",
@@ -136,7 +140,6 @@
"com.android.hardware.input.input-aconfig",
"com.android.input.flags-aconfig",
"com.android.media.flags.bettertogether-aconfig",
- "com.android.net.flags-aconfig",
"com.android.net.thread.flags-aconfig",
"com.android.server.flags.services-aconfig",
"com.android.text.flags-aconfig",
@@ -775,9 +778,10 @@
// Networking
aconfig_declarations {
- name: "com.android.net.flags-aconfig",
- package: "com.android.net.flags",
+ name: "android.net.platform.flags-aconfig",
+ package: "android.net.platform.flags",
srcs: ["core/java/android/net/flags.aconfig"],
+ visibility: [":__subpackages__"],
}
// Thread network
@@ -788,9 +792,10 @@
}
java_aconfig_library {
- name: "com.android.net.flags-aconfig-java",
- aconfig_declarations: "com.android.net.flags-aconfig",
+ name: "android.net.platform.flags-aconfig-java",
+ aconfig_declarations: "android.net.platform.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ visibility: [":__subpackages__"],
}
java_aconfig_library {
@@ -1092,4 +1097,29 @@
name: "android.crashrecovery.flags-aconfig-java",
aconfig_declarations: "android.crashrecovery.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
\ No newline at end of file
+}
+
+// Backup
+java_aconfig_library {
+ name: "backup_flags_lib",
+ aconfig_declarations: "backup_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Wifi
+aconfig_declarations {
+ name: "android.net.wifi.flags-aconfig",
+ package: "android.net.wifi.flags",
+ srcs: ["wifi/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.net.wifi.flags-aconfig-java",
+ aconfig_declarations: "android.net.wifi.flags-aconfig",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.wifi",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/OWNERS b/OWNERS
index 733157f..935b768 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,3 +39,5 @@
per-file *Ravenwood* = file:ravenwood/OWNERS
per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
+
+per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
\ No newline at end of file
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index e4549b4..eb5842b 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,3 @@
-chiuwinson@google.com
+alexbuy@google.com
patb@google.com
schfan@google.com
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index aadbc23..add0a08 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -16,8 +16,6 @@
package android.input
-import android.content.Context
-import android.content.res.Resources
import android.os.SystemProperties
import android.perftests.utils.PerfStatusReporter
import android.view.InputDevice
@@ -38,8 +36,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
import java.time.Duration
@@ -68,18 +64,6 @@
InputDevice.SOURCE_STYLUS, /*flags=*/0)
}
-private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context {
- val context = mock(Context::class.java)
- val resources: Resources = mock(Resources::class.java)
- `when`(context.getResources()).thenReturn(resources)
- `when`(resources.getInteger(
- com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn(
- offset.toNanos().toInt())
- `when`(resources.getBoolean(
- com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction)
- return context
-}
-
@RunWith(AndroidJUnit4::class)
@LargeTest
class MotionPredictorBenchmark {
@@ -115,7 +99,7 @@
var eventPosition = 0f
val positionInterval = 10f
- val predictor = MotionPredictor(getPredictionContext(offset, /*enablePrediction=*/true))
+ val predictor = MotionPredictor(/*isPredictionEnabled=*/true, offset.toNanos().toInt())
// ACTION_DOWN t=0 x=0 y=0
predictor.record(getStylusMotionEvent(
eventTime, ACTION_DOWN, /*x=*/eventPosition, /*y=*/eventPosition))
@@ -141,12 +125,11 @@
*/
@Test
fun timeCreatePredictor() {
- val context = getPredictionContext(
- /*offset=*/Duration.ofMillis(20), /*enablePrediction=*/true)
+ val offsetNanos = Duration.ofMillis(20).toNanos().toInt()
val state = perfStatusReporter.getBenchmarkState()
while (state.keepRunning()) {
- MotionPredictor(context)
+ MotionPredictor(/*isPredictionEnabled=*/true, offsetNanos)
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index e08200b..33f6899 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -743,7 +743,8 @@
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(TARGET_OP,
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index abf8008..39de0af 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2026,8 +2026,8 @@
iAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName)
- throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
final int userId = UserHandle.getUserId(uid);
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM
|| !isExactAlarmChangeEnabled(packageName, userId)) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 357e1396..6635484 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -227,7 +227,7 @@
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index e589c21..19bc716 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -671,7 +671,8 @@
/*packageName=*/ null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
final int userId = UserHandle.getUserId(uid);
synchronized (mSystemExemptionAppOpMode) {
mSystemExemptionAppOpMode.delete(uid);
diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING
index 24ba5c4..f1e4d0ee 100644
--- a/core/TEST_MAPPING
+++ b/core/TEST_MAPPING
@@ -23,7 +23,7 @@
],
"postsubmit": [
{
- "name": "ContactKeysManagerTest",
+ "name": "CtsContactKeysManagerTestCases",
"options": [
{
"include-filter": "android.provider.cts.contactkeys."
diff --git a/core/api/current.txt b/core/api/current.txt
index 55ea2f4..cf5a261 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5095,10 +5095,12 @@
public static interface AppOpsManager.OnOpActiveChangedListener {
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, int, boolean, int, int);
}
public static interface AppOpsManager.OnOpChangedListener {
method public void onOpChanged(String, String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpChanged(@NonNull String, @NonNull String, int, @NonNull String);
}
public abstract static class AppOpsManager.OnOpNotedCallback {
@@ -6353,6 +6355,7 @@
field public static final String CATEGORY_STOPWATCH = "stopwatch";
field public static final String CATEGORY_SYSTEM = "sys";
field public static final String CATEGORY_TRANSPORT = "transport";
+ field @FlaggedApi("android.app.category_voicemail") public static final String CATEGORY_VOICEMAIL = "voicemail";
field public static final String CATEGORY_WORKOUT = "workout";
field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification> CREATOR;
@@ -12391,6 +12394,7 @@
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -15570,6 +15574,7 @@
method public boolean clipRect(float, float, float, float);
method public boolean clipRect(int, int, int, int);
method public void concat(@Nullable android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16218,6 +16223,24 @@
enum_constant public static final android.graphics.Matrix.ScaleToFit START;
}
+ @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public class Matrix44 {
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public float[] map(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void map(float, float, float, float, @NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
+ }
+
public class Mesh {
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.RectF);
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.RectF);
@@ -21171,6 +21194,7 @@
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
method public float getStreamVolumeDb(int, int, int);
+ method @FlaggedApi("android.media.audio.supported_device_types_api") @NonNull public java.util.Set<java.lang.Integer> getSupportedDeviceTypes(int);
method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
method @Deprecated public int getVibrateSetting(int);
method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
@@ -25789,6 +25813,7 @@
field public static final int FINAL_STATE_CANCELED = 2; // 0x2
field public static final int FINAL_STATE_ERROR = 3; // 0x3
field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
}
@FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
@@ -25796,7 +25821,7 @@
method @NonNull public android.media.metrics.EditingEndedEvent build();
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
- method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
}
public final class EditingSession implements java.lang.AutoCloseable {
@@ -33144,7 +33169,7 @@
method public static long getStartRequestedElapsedRealtime();
method public static long getStartRequestedUptimeMillis();
method public static long getStartUptimeMillis();
- method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
+ method @IntRange(from=0xffffffec, to=android.os.Process.THREAD_PRIORITY_LOWEST) public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
method public static final int getUidForName(String);
method public static final boolean is64Bit();
method public static boolean isApplicationUid(int);
@@ -33159,8 +33184,8 @@
method public static final int myUid();
method public static android.os.UserHandle myUserHandle();
method public static final void sendSignal(int, int);
- method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
- method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
+ method public static final void setThreadPriority(int, @IntRange(from=0xffffffec, to=android.os.Process.THREAD_PRIORITY_LOWEST) int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
+ method public static final void setThreadPriority(@IntRange(from=0xffffffec, to=android.os.Process.THREAD_PRIORITY_LOWEST) int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method @Deprecated public static final boolean supportsProcesses();
field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
@@ -50648,6 +50673,7 @@
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
field public static final int KEYCODE_ENDCALL = 6; // 0x6
field public static final int KEYCODE_ENTER = 66; // 0x42
field public static final int KEYCODE_ENVELOPE = 65; // 0x41
@@ -50780,6 +50806,7 @@
field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
field public static final int KEYCODE_RO = 217; // 0xd9
field public static final int KEYCODE_S = 47; // 0x2f
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74
field public static final int KEYCODE_SEARCH = 84; // 0x54
field public static final int KEYCODE_SEMICOLON = 74; // 0x4a
@@ -59541,7 +59568,6 @@
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
- method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 24b9233..55ed1f5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -66,6 +66,11 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
+ public final class SystemServiceRegistry {
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") @Nullable public static Object getSystemServiceWithNoContext(@NonNull String);
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static <TServiceClass> void registerForeverStaticService(@NonNull String, @NonNull Class<TServiceClass>, @NonNull android.app.SystemServiceRegistry.StaticServiceProducerWithBinder<TServiceClass>);
+ }
+
}
package android.app.admin {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 708f10e..86f2b628 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -822,6 +822,7 @@
public static interface AppOpsManager.OnOpNotedListener {
method public void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int, int);
}
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
@@ -3208,8 +3209,14 @@
package android.companion.virtual {
+ public final class VirtualDevice implements android.os.Parcelable {
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomAudioInputSupport();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomCameraSupport();
+ }
+
public final class VirtualDeviceManager {
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+ method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @NonNull public java.util.Set<java.lang.String> getAllPersistentDeviceIds();
method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
@@ -9657,6 +9664,7 @@
public final class DeviceWiphyCapabilities implements android.os.Parcelable {
ctor public DeviceWiphyCapabilities();
method public int describeContents();
+ method @FlaggedApi("android.net.wifi.flags.get_device_cross_akm_roaming_support") public int getMaxNumberAkms();
method public int getMaxNumberRxSpatialStreams();
method public int getMaxNumberTxSpatialStreams();
method public boolean isChannelWidthSupported(int);
@@ -14848,11 +14856,11 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableCellularIdentifierDisclosureNotifications(boolean);
- method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableNullCipherNotifications(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setNrDualConnectivityState(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNullCipherNotificationsEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c546d7a..77add41 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3616,7 +3616,7 @@
method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 316; // 0x13c
+ field public static final int LAST_KEYCODE = 318; // 0x13e
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 00c4b0f..7907059 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,6 +36,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -1555,9 +1556,24 @@
*/
public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+ /**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final int OP_UNARCHIVAL_CONFIRMATION =
+ AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 145;
+ public static final int _NUM_OP = 147;
/**
* All app ops represented as strings.
@@ -1708,6 +1724,8 @@
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
OPSTR_RUN_BACKUP_JOBS,
+ OPSTR_ARCHIVE_ICON_OVERLAY,
+ OPSTR_UNARCHIVAL_CONFIRMATION,
})
public @interface AppOpString {}
@@ -2048,6 +2066,20 @@
public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
/**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay";
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -2520,6 +2552,8 @@
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OP_RUN_BACKUP_JOBS,
+ OP_ARCHIVE_ICON_OVERLAY,
+ OP_UNARCHIVAL_CONFIRMATION,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2979,6 +3013,12 @@
.build(),
new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS")
.setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(),
+ new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY,
+ "ARCHIVE_ICON_OVERLAY")
+ .setDefaultMode(MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION,
+ "UNARCHIVAL_CONFIRMATION")
+ .setDefaultMode(MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
@@ -3113,7 +3153,7 @@
/**
* Retrieve the permission associated with an operation, or null if there is not one.
- *
+
* @param op The operation name.
*
* @hide
@@ -7307,6 +7347,12 @@
* The default impl is to fallback onto {@link #onOpChanged(String, String)
*
+ * <p> Implement this method and not
+ * {@link #onOpChanged(String, String, int, String)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT}.
+ *
* @param op The Op that changed.
* @param packageName Package of the app whose Op changed.
* @param userId User Space of the app whose Op changed.
@@ -7315,6 +7361,31 @@
default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId) {
onOpChanged(op, packageName);
}
+
+ /**
+ * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which
+ * the op mode has changed.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op changes
+ * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpChanged(String, String, int)}
+ * will not be called automatically.
+ *
+ * @param op The Op that changed.
+ * @param packageName Package of the app whose Op changed.
+ * @param userId User id of the app whose Op changed.
+ * @param persistentDeviceId persistent device id whose Op changed.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId,
+ @NonNull String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpChanged(op, packageName, userId);
+ }
+ }
}
/**
@@ -7334,6 +7405,12 @@
/**
* Called when the active state of an app-op changes.
*
+ * <p> Implement this method and not
+ * {@link #onOpActiveChanged(String, int, String, String, int, boolean, int, int)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that changed.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7349,6 +7426,37 @@
int attributionFlags, int attributionChainId) {
onOpActiveChanged(op, uid, packageName, active);
}
+
+ /**
+ * Similar to {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op state
+ * changes on the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented,
+ * {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that changed.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The operation's attribution tag.
+ * @param virtualDeviceId the virtual device id whose operation has changed
+ * @param active Whether the operation became active or inactive.
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpActiveChanged(op, uid, packageName, attributionTag, active, attributionFlags,
+ attributionChainId);
+ }
+ }
}
/**
@@ -7361,6 +7469,10 @@
/**
* Called when an app-op is noted.
*
+ * <p> Implement this method and not
+ * {@link #onOpNoted(String, int, String, String, int, int, int)} if callbacks are
+ * required only on op notes for the default device {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that was noted.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7370,6 +7482,34 @@
*/
void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @OpFlags int flags, @Mode int result);
+
+ /**
+ * Similar to {@link #onOpNoted(String, int, String, String, int, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required for op notes on all devices.
+ * If not implemented explicitly, the default implementation will notify for the
+ * default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpNoted(String, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that was noted.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that noted the operation
+ * @param flags The flags of this op
+ * @param result The result of the note.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpNoted(op, uid, packageName, attributionTag, flags, result);
+ }
+ }
}
/**
@@ -7408,6 +7548,9 @@
public static class OnOpChangedInternalListener implements OnOpChangedListener {
public void onOpChanged(String op, String packageName) { }
public void onOpChanged(int op, String packageName) { }
+ public void onOpChanged(int op, String packageName, String persistentDeviceId) {
+ onOpChanged(op, packageName);
+ }
}
/**
@@ -7418,6 +7561,8 @@
public interface OnOpActiveChangedInternalListener extends OnOpActiveChangedListener {
default void onOpActiveChanged(String op, int uid, String packageName, boolean active) { }
default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
+ default void onOpActiveChanged(int op, int uid, String packageName, int virtualDeviceId,
+ boolean active) { }
}
/**
@@ -7471,6 +7616,12 @@
/**
* Called when an op was started.
*
+ * <p> Implement this method and not
+ * {@link #onOpStarted(int, int, String, String, int, int, int, int, int, int)} if
+ * callbacks are
+ * required only on op starts for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* Note: This is only for op starts. It is not called when an op is noted or stopped.
* By default, unless this method is overridden, no code will be executed for resume
* events.
@@ -7491,6 +7642,37 @@
onOpStarted(op, uid, packageName, attributionTag, flags, result);
}
}
+
+ /**
+ * Similar to {@link #onOpStarted(int, int, String, String, int, int)},
+ * but also includes the virtual device id that started the op.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op starts on
+ * the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpStarted(int, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that started the operation
+ * @param flags The flags of this op.
+ * @param result The result of the start.
+ * @param startType The start type of this start event. Either failed, resumed, or started.
+ * @param attributionFlags The location of this started op in an attribution chain.
+ */
+ default void onOpStarted(int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result, @StartedType int startType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpStarted(op, uid, packageName, attributionTag, flags, result, startType,
+ attributionFlags, attributionChainId);
+ }
+ }
}
AppOpsManager(Context context, IAppOpsService service) {
@@ -8015,14 +8197,16 @@
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (callback instanceof OnOpChangedInternalListener) {
- ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
+ ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
+ persistentDeviceId);
}
if (sAppOpInfos[op].name != null) {
callback.onOpChanged(sAppOpInfos[op].name, packageName,
- UserHandle.getUserId(uid));
+ UserHandle.getUserId(uid), persistentDeviceId);
}
}
};
@@ -8103,16 +8287,17 @@
cb = new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName,
- String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
executor.execute(() -> {
if (callback instanceof OnOpActiveChangedInternalListener) {
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
- uid, packageName, active);
+ uid, packageName, virtualDeviceId, active);
}
if (sAppOpInfos[op].name != null) {
callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
- attributionTag, active, attributionFlags, attributionChainId);
+ attributionTag, virtualDeviceId, active, attributionFlags,
+ attributionChainId);
}
});
}
@@ -8181,10 +8366,10 @@
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode, int startType, int attributionFlags,
- int attributionChainId) {
- callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
- startType, attributionFlags, attributionChainId);
+ int virtualDeviceId, int flags, int mode, int startType,
+ int attributionFlags, int attributionChainId) {
+ callback.onOpStarted(op, uid, packageName, attributionTag, virtualDeviceId,
+ flags, mode, startType, attributionFlags, attributionChainId);
}
};
mStartedWatchers.put(callback, cb);
@@ -8352,13 +8537,13 @@
cb = new IAppOpsNotedCallback.Stub() {
@Override
public void opNoted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode) {
+ int virtualDeviceId, int flags, int mode) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
if (sAppOpInfos[op].name != null) {
listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
- attributionTag,
+ attributionTag, virtualDeviceId,
flags, mode);
}
});
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 34c44f9..4f1db7d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -4032,7 +4032,8 @@
private Drawable getArchivedAppIcon(String packageName) {
try {
return new BitmapDrawable(null,
- mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
+ mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()),
+ mContext.getPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a81ad3c..d705eeb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1061,6 +1061,12 @@
public static final String CATEGORY_MISSED_CALL = "missed_call";
/**
+ * Notification category: voicemail.
+ */
+ @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL)
+ public static final String CATEGORY_VOICEMAIL = "voicemail";
+
+ /**
* One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 3b5bba2..729f92a 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -111,5 +111,9 @@
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
+# Multitasking
+per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
+per-file multitasking.aconfig = file:/libs/WindowManager/Shell/OWNERS
+
# Zygote
per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 397b63f..ba9c895 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -19,6 +19,7 @@
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.adservices.AdServicesFrameworkInitializer;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -1617,6 +1618,19 @@
return new ContactKeysManager(ctx);
}});
+ // DO NOT do a flag check like this unless the flag is read-only.
+ // (because this code is executed during preload in zygote.)
+ // If the flag is mutable, the check should be inside CachedServiceFetcher.
+ if (Flags.bicClient()) {
+ registerService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE,
+ BackgroundInstallControlManager.class,
+ new CachedServiceFetcher<BackgroundInstallControlManager>() {
+ @Override
+ public BackgroundInstallControlManager createService(ContextImpl ctx) {
+ return new BackgroundInstallControlManager(ctx);
+ }
+ });
+ }
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
@@ -1668,11 +1682,7 @@
return new Object[sServiceCacheSize];
}
- /**
- * Gets a system service from a given context.
- * @hide
- */
- public static Object getSystemService(ContextImpl ctx, String name) {
+ private static ServiceFetcher<?> getSystemServiceFetcher(String name) {
if (name == null) {
return null;
}
@@ -1683,6 +1693,18 @@
}
return null;
}
+ return fetcher;
+ }
+
+ /**
+ * Gets a system service from a given context.
+ * @hide
+ */
+ public static Object getSystemService(@NonNull ContextImpl ctx, String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
final Object ret = fetcher.getService(ctx);
if (sEnableServiceNotFoundWtf && ret == null) {
@@ -1710,6 +1732,26 @@
}
/**
+ * Gets a system service which has opted-in to being fetched without a context.
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @Nullable Object getSystemServiceWithNoContext(@NonNull String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
+
+ if (!fetcher.supportsFetchWithoutContext()) {
+ throw new IllegalArgumentException(
+ "Manager cannot be fetched without a context: " + name);
+ }
+
+ return fetcher.getService(null);
+ }
+
+ /**
* Gets the name of the system-level service that is represented by the specified class.
* @hide
*/
@@ -1863,6 +1905,50 @@
}
/**
+ * Used by apex modules to register a "service wrapper" that is not tied to any {@link Context}
+ * and will never require a context in the future.
+ *
+ * Services registered in this way can be fetched via
+ * {@link #getSystemServiceWithNoContext(String)}, so cannot require a context in future without
+ * a breaking change.
+ *
+ * <p>This can only be called from the methods called by the static initializer of
+ * {@link SystemServiceRegistry}. (Otherwise it throws a {@link IllegalStateException}.)
+ *
+ * @param serviceName the name of the binder object, such as
+ * {@link Context#JOB_SCHEDULER_SERVICE}.
+ * @param serviceWrapperClass the wrapper class, such as the class of
+ * {@link android.app.job.JobScheduler}.
+ * @param serviceProducer Callback that takes the service binder object with the name
+ * {@code serviceName} and returns an actual service wrapper instance.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static <TServiceClass> void registerForeverStaticService(
+ @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
+ @NonNull StaticServiceProducerWithBinder<TServiceClass> serviceProducer) {
+ ensureInitializing("registerStaticService");
+ Preconditions.checkStringNotEmpty(serviceName);
+ Objects.requireNonNull(serviceWrapperClass);
+ Objects.requireNonNull(serviceProducer);
+
+ registerService(serviceName, serviceWrapperClass,
+ new StaticServiceFetcher<TServiceClass>() {
+ @Override
+ public TServiceClass createService() throws ServiceNotFoundException {
+ return serviceProducer.createService(
+ ServiceManager.getServiceOrThrow(serviceName));
+ }
+
+ @Override
+ public boolean supportsFetchWithoutContext() {
+ return true;
+ }});
+ }
+
+ /**
* Similar to {@link #registerStaticService(String, Class, StaticServiceProducerWithBinder)},
* but used for a "service wrapper" that doesn't take a service binder in its constructor.
*
@@ -1952,6 +2038,18 @@
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
+
+ /**
+ * Should this service fetcher support being fetched via {@link #getSystemService(String)},
+ * without a Context?
+ *
+ * This means that the service cannot depend on a Context in future!
+ *
+ * @return true if this is supported for this service.
+ */
+ default boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2059,6 +2157,11 @@
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
+
+ // Services that explicitly use a Context can never be fetched without one.
+ public final boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2083,6 +2186,13 @@
}
public abstract T createService() throws ServiceNotFoundException;
+
+ // Services that do not need a Context can potentially be fetched without one, but the
+ // default is false, so that the service can require one in future without this being a
+ // breaking change.
+ public boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/** @hide */
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 6b558d0..ffbd80c 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -43,12 +43,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -1283,6 +1285,14 @@
// And bring live SharedPreferences instances up to date
reloadSharedPreferences();
+ // It's possible that onRestoreFile was overridden and that the agent did not
+ // consume all the data for this file from the pipe. We need to clear the pipe,
+ // otherwise the framework can get stuck trying to write to a full pipe or
+ // onRestoreFile could be called with the previous file's data left in the pipe.
+ if (Flags.enableClearPipeAfterRestoreFile()) {
+ clearUnconsumedDataFromPipe(data, size);
+ }
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
@@ -1296,6 +1306,16 @@
}
}
+ private static void clearUnconsumedDataFromPipe(ParcelFileDescriptor data, long size) {
+ try (FileInputStream in = new FileInputStream(data.getFileDescriptor())) {
+ if (in.available() > 0) {
+ in.skip(size);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to clear unconsumed data from pipe.", e);
+ }
+ }
+
@Override
public void doRestoreFinished(int token, IBackupManager callbackBinder) {
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
new file mode 100644
index 0000000..ab00891
--- /dev/null
+++ b/core/java/android/app/multitasking.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "enable_pip_ui_state_callback_on_entering"
+ namespace: "multitasking"
+ description: "Enables PiP UI state callback on entering"
+ bug: "303718131"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index d11c6c5..a5d4a14 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -43,3 +43,10 @@
description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
bug: "309920145"
}
+
+flag {
+ name: "category_voicemail"
+ namespace: "wear_sysui"
+ description: "Adds a new voicemail category for notifications"
+ bug: "322806700"
+}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index d0c8be6..97fa2ba 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -17,11 +17,14 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.content.Context;
import android.os.Parcel;
@@ -164,6 +167,44 @@
}
}
+ /**
+ * Returns whether this device may have custom audio input device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomAudioInputSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM;
+ // TODO(b/291735254): also check for a custom audio injection mix for this device id.
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether this device may have custom cameras.
+ *
+ * <p>Returning {@code true} does not necessarily mean that this device has cameras, it only
+ * means that a {@link android.hardware.camera2.CameraManager} instance created from a
+ * {@link Context} associated with this device will return this device's cameras, if any.</p>
+ *
+ * @see Context#getDeviceId()
+ * @see Context#createDeviceContext(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomCameraSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA) == DEVICE_POLICY_CUSTOM;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 90d251b..a16e94a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
@@ -74,9 +75,11 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -389,6 +392,28 @@
}
/**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ *
+ * @hide
+ */
+ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+ @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+ @SystemApi
+ @NonNull
+ public Set<String> getAllPersistentDeviceIds() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve persistent ids; no virtual device manager service.");
+ return Collections.emptySet();
+ }
+ try {
+ return new ArraySet<>(mService.getAllPersistentDeviceIds());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
* {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
* device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bde562d..33b1134 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -409,6 +409,7 @@
"packageName = " + mAttributionSourceState.packageName + ", " +
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
+ "deviceId = " + mAttributionSourceState.deviceId + ", " +
"next = " + (mAttributionSourceState.next != null
&& mAttributionSourceState.next.length > 0
? mAttributionSourceState.next[0] : null) +
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index a97de63..62db65f 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -128,4 +128,6 @@
/** Unregister a callback, so that it won't be called when LauncherApps dumps. */
void unRegisterDumpCallback(IDumpCallback cb);
+
+ void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6dc8d47..380de96 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -840,7 +840,7 @@
ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
- Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
+ Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName);
boolean isAppArchivable(String packageName, in UserHandle user);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 1d2b1af..50be983 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1801,6 +1801,31 @@
}
}
+ /**
+ * Enable or disable different archive compatibility options of the launcher.
+ *
+ * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
+ * that a certain app is archived. True by default.
+ * Launchers might want to disable this operation if they want to provide custom user experience
+ * to differentiate archived apps.
+ * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
+ * they click an archived app, which explains that the app will be downloaded and restored in
+ * the background. True by default.
+ * Launchers might want to disable this operation if they provide sufficient, alternative user
+ * guidance to highlight that an unarchival is starting and ongoing once an archived app is
+ * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ try {
+ mService.setArchiveCompatibilityOptions(enableIconOverlay,
+ enableUnarchivalConfirmation);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/** @return position in mCallbacks for callback or -1 if not present. */
private int findCallbackLocked(Callback callback) {
if (callback == null) {
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0396443..1c36a88 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1575,7 +1575,7 @@
}
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == AppOpsManager.OP_PLAY_AUDIO) {
final Camera camera = mWeakCamera.get();
if (camera != null) {
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index fdbd319..89fa5fb 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -19,6 +19,7 @@
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
+import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import android.Manifest;
@@ -303,6 +304,53 @@
}
/**
+ * Returns true if the feature flag for touchpad tap dragging is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadTapDraggingFeatureFlagEnabled() {
+ return touchpadTapDragging();
+ }
+
+ /**
+ * Returns true if the touchpad should allow tap dragging.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether the touchpad should allow tap dragging.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadTapDragging(@NonNull Context context) {
+ if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
+ return false;
+ }
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_TAP_DRAGGING, 0, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * Sets the tap dragging behavior for the touchpad.
+ *
+ * The new behavior is only applied to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @param enabled Will enable tap dragging if true, disable it if false
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadTapDragging(@NonNull Context context, boolean enabled) {
+ if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_TAP_DRAGGING, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Returns true if the touchpad should use the right click zone.
*
* The returned value only applies to gesture-compatible touchpads.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0ed6569..e070fe5 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -33,7 +33,21 @@
flag {
namespace: "input_native"
+ name: "emoji_and_screenshot_keycodes_available"
+ description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots"
+ bug: "315307777"
+}
+
+flag {
+ namespace: "input_native"
name: "keyboard_a11y_slow_keys_flag"
description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
bug: "294546335"
+}
+
+flag {
+ namespace: "input_native"
+ name: "touchpad_tap_dragging"
+ description: "Offers a setting to enable touchpad tap dragging"
+ bug: "321978150"
}
\ No newline at end of file
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 0ad1804..311dc09 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -1,50 +1,11 @@
-package: "com.android.net.flags"
+package: "android.net.platform.flags"
-flag {
- name: "track_multiple_network_activities"
- namespace: "android_core_networking"
- description: "NetworkActivityTracker tracks multiple networks including non default networks"
- bug: "267870186"
-}
-
-flag {
- name: "forbidden_capability"
- namespace: "android_core_networking"
- description: "This flag controls the forbidden capability API"
- bug: "302997505"
-}
-
-flag {
- name: "set_data_saver_via_cm"
- namespace: "android_core_networking"
- description: "Set data saver through ConnectivityManager API"
- bug: "297836825"
-}
-
-flag {
- name: "support_is_uid_networking_blocked"
- namespace: "android_core_networking"
- description: "This flag controls whether isUidNetworkingBlocked is supported"
- bug: "297836825"
-}
-
-flag {
- name: "basic_background_restrictions_enabled"
- namespace: "android_core_networking"
- description: "Block network access for apps in a low importance background state"
- bug: "304347838"
-}
-
-flag {
- name: "register_nsd_offload_engine"
- namespace: "android_core_networking"
- description: "The flag controls the access for registerOffloadEngine API in NsdManager"
- bug: "294777050"
-}
+# This file contains aconfig flags used from platform code
+# Flags used for module APIs must be in aconfig files under each modules
flag {
name: "ipsec_transform_state"
- namespace: "android_core_networking_ipsec"
+ namespace: "core_networking_ipsec"
description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
bug: "308011229"
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 05b7827f..b7556df 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -292,7 +292,7 @@
sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
}
- private static ThreadLocal<SomeArgs> sIdentity$ravenwood;
+ private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
private static class IdentitySupplier implements Supplier<SomeArgs> {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1f3a162..7020a38 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -20,6 +20,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -844,7 +845,7 @@
return "amd64".equals(System.getProperty("os.arch"));
}
- private static ThreadLocal<SomeArgs> sIdentity$ravenwood;
+ private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
@@ -1122,7 +1123,8 @@
* priority.
*/
@android.ravenwood.annotation.RavenwoodReplace
- public static final native void setThreadPriority(int tid, int priority)
+ public static final native void setThreadPriority(int tid,
+ @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
/** @hide */
@@ -1288,7 +1290,8 @@
* @see #setThreadPriority(int, int)
*/
@android.ravenwood.annotation.RavenwoodReplace
- public static final native void setThreadPriority(int priority)
+ public static final native void setThreadPriority(
+ @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
/** @hide */
@@ -1310,6 +1313,7 @@
* <var>tid</var> does not exist.
*/
@android.ravenwood.annotation.RavenwoodReplace
+ @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
public static final native int getThreadPriority(int tid)
throws IllegalArgumentException;
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee64..a925564 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 554432
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
+
zyy@google.com
-patb@google.com
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 92e4967..bcdb982 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -35,6 +35,7 @@
import android.util.IndentingPrintWriter;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
* List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -51,6 +52,8 @@
private final float mHapticChannelMaxVibrationAmplitude;
private final int mRampStepDurationMs;
private final int mRampDownDurationMs;
+ private final int mRequestVibrationParamsTimeoutMs;
+ private final int[] mRequestVibrationParamsForUsages;
private final boolean mIgnoreVibrationsOnWirelessCharger;
@@ -75,6 +78,10 @@
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
mRampStepDurationMs = loadInteger(resources,
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
+ mRequestVibrationParamsTimeoutMs = loadInteger(resources,
+ com.android.internal.R.integer.config_requestVibrationParamsTimeout, 0);
+ mRequestVibrationParamsForUsages = loadIntArray(resources,
+ com.android.internal.R.array.config_requestVibrationParamsForUsages);
mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
@@ -115,6 +122,10 @@
return res != null ? res.getBoolean(resId) : defaultValue;
}
+ private static int[] loadIntArray(@Nullable Resources res, int resId) {
+ return res != null ? res.getIntArray(resId) : new int[0];
+ }
+
/**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
@@ -140,6 +151,23 @@
}
/**
+ * The duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return Math.max(mRequestVibrationParamsTimeoutMs, 0);
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mRequestVibrationParamsForUsages;
+ }
+
+ /**
* The duration, in milliseconds, that should be applied to convert vibration effect's
* {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
* devices without PWLE support.
@@ -204,6 +232,9 @@
+ ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+ ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs
+ + ", mRequestVibrationParamsForUsages=" + Arrays.toString(
+ getRequestVibrationParamsForUsagesNames())
+ "}";
}
@@ -220,4 +251,14 @@
pw.println("rampDownDurationMs = " + mRampDownDurationMs);
pw.decreaseIndent();
}
+
+ private String[] getRequestVibrationParamsForUsagesNames() {
+ int usagesCount = mRequestVibrationParamsForUsages.length;
+ String[] names = new String[usagesCount];
+ for (int i = 0; i < usagesCount; i++) {
+ names[i] = VibrationAttributes.usageToString(mRequestVibrationParamsForUsages[i]);
+ }
+
+ return names;
+ }
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 5d6dfc7..7d127ad 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -2102,7 +2102,7 @@
PhoneAccountHandle accountHandle) {
TelecomManager tm = null;
try {
- tm = TelecomManager.from(context);
+ tm = context.getSystemService(TelecomManager.class);
} catch (UnsupportedOperationException e) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "No TelecomManager found to get account address.");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e238080..524b733 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6049,6 +6049,13 @@
public static final String TOUCHPAD_TAP_TO_CLICK = "touchpad_tap_to_click";
/**
+ * Whether to enable tap dragging on touchpads.
+ *
+ * @hide
+ */
+ public static final String TOUCHPAD_TAP_DRAGGING = "touchpad_tap_dragging";
+
+ /**
* Whether to enable a right-click zone on touchpads.
*
* When set to 1, pressing to click in a section on the right-hand side of the touchpad will
@@ -6270,6 +6277,7 @@
PRIVATE_SETTINGS.add(TOUCHPAD_POINTER_SPEED);
PRIVATE_SETTINGS.add(TOUCHPAD_NATURAL_SCROLLING);
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_TO_CLICK);
+ PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index af688e9..2841dc0 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3202,7 +3202,7 @@
/**
* The infrastructure bitmask which the APN can be used on. For example, some APNs can only
* be used when the device is on cellular, on satellite, or both. The default value is
- * 1 (INFRASTRUCTURE_CELLULAR).
+ * 3 (INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE).
*
* <P>Type: INTEGER</P>
* @hide
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 533d459..8bd6c85 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 36824
brambonne@google.com
-brufino@google.com
+eranm@google.com
jeffv@google.com
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 3008b8d..446fe3d 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -8,14 +8,6 @@
}
flag {
- name: "notification_lifetime_extension_refactor"
- namespace: "systemui"
- description: "Enables moving notification lifetime extension management from SystemUI to "
- "Notification Manager Service"
- bug: "299448097"
-}
-
-flag {
name: "redact_sensitive_notifications_from_untrusted_listeners"
namespace: "systemui"
description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices"
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 94c8eaf..783f3b7 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -26,7 +26,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.icu.util.ULocale;
@@ -240,6 +239,23 @@
private final @IntRange(from = 0) int mIndex;
private final @NonNull String mFontVariationSettings;
private final @Nullable String mFontFamilyName;
+ private final @VarTypeAxes int mVarTypeAxes;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "VAR_TYPE_AXES_" }, value = {
+ VAR_TYPE_AXES_NONE,
+ VAR_TYPE_AXES_WGHT,
+ VAR_TYPE_AXES_ITAL,
+ })
+ public @interface VarTypeAxes {}
+
+ /** @hide */
+ public static final int VAR_TYPE_AXES_NONE = 0;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_WGHT = 1;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_ITAL = 2;
/**
* Construct a Font instance.
@@ -248,7 +264,8 @@
*/
public Font(@NonNull File file, @Nullable File originalFile, @NonNull String postScriptName,
@NonNull FontStyle style, @IntRange(from = 0) int index,
- @NonNull String fontVariationSettings, @Nullable String fontFamilyName) {
+ @NonNull String fontVariationSettings, @Nullable String fontFamilyName,
+ @VarTypeAxes int varTypeAxes) {
mFile = file;
mOriginalFile = originalFile;
mPostScriptName = postScriptName;
@@ -256,6 +273,7 @@
mIndex = index;
mFontVariationSettings = fontVariationSettings;
mFontFamilyName = fontFamilyName;
+ mVarTypeAxes = varTypeAxes;
}
@Override
@@ -273,6 +291,7 @@
dest.writeInt(mIndex);
dest.writeString8(mFontVariationSettings);
dest.writeString8(mFontFamilyName);
+ dest.writeInt(mVarTypeAxes);
}
public static final @NonNull Creator<Font> CREATOR = new Creator<Font>() {
@@ -288,9 +307,10 @@
int index = source.readInt();
String varSettings = source.readString8();
String fallback = source.readString8();
+ int varTypeAxes = source.readInt();
return new Font(path, originalPath, postScriptName, new FontStyle(weight, slant),
- index, varSettings, fallback);
+ index, varSettings, fallback, varTypeAxes);
}
@Override
@@ -366,6 +386,15 @@
}
/**
+ * Returns the list of supported axes tags for variable family type resolution.
+ *
+ * @hide
+ */
+ public @VarTypeAxes int getVarTypeAxes() {
+ return mVarTypeAxes;
+ }
+
+ /**
* Returns the list of axes associated to this font.
* @deprecated Use getFontVariationSettings
* @hide
@@ -408,13 +437,14 @@
&& Objects.equals(mOriginalFile, font.mOriginalFile)
&& Objects.equals(mStyle, font.mStyle)
&& Objects.equals(mFontVariationSettings, font.mFontVariationSettings)
- && Objects.equals(mFontFamilyName, font.mFontFamilyName);
+ && Objects.equals(mFontFamilyName, font.mFontFamilyName)
+ && mVarTypeAxes == font.mVarTypeAxes;
}
@Override
public int hashCode() {
return Objects.hash(mFile, mOriginalFile, mStyle, mIndex, mFontVariationSettings,
- mFontFamilyName);
+ mFontFamilyName, mVarTypeAxes);
}
@Override
@@ -426,6 +456,7 @@
+ ", mIndex=" + mIndex
+ ", mFontVariationSettings='" + mFontVariationSettings + '\''
+ ", mFontFamilyName='" + mFontFamilyName + '\''
+ + ", mVarTypeAxes='" + mVarTypeAxes + '\''
+ '}';
}
}
@@ -549,7 +580,6 @@
private final @NonNull List<Font> mFonts;
private final @NonNull LocaleList mLocaleList;
private final @Variant int mVariant;
- private final int mVariableFontFamilyType;
/** @hide */
@Retention(SOURCE)
@@ -589,11 +619,10 @@
* @hide Only system server can create this instance and passed via IPC.
*/
public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
- @Variant int variant, int variableFontFamilyType) {
+ @Variant int variant) {
mFonts = fonts;
mLocaleList = localeList;
mVariant = variant;
- mVariableFontFamilyType = variableFontFamilyType;
}
/**
@@ -644,20 +673,6 @@
return mVariant;
}
- /**
- * Returns the font family type.
- *
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_NONE
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
- * @hide
- * @return variable font family type.
- */
- public @VariableFontFamilyType int getVariableFontFamilyType() {
- return mVariableFontFamilyType;
- }
-
@Override
public int describeContents() {
return 0;
@@ -668,7 +683,6 @@
dest.writeTypedList(mFonts, flags);
dest.writeString8(mLocaleList.toLanguageTags());
dest.writeInt(mVariant);
- dest.writeInt(mVariableFontFamilyType);
}
public static final @NonNull Creator<FontFamily> CREATOR = new Creator<FontFamily>() {
@@ -679,10 +693,8 @@
source.readTypedList(fonts, Font.CREATOR);
String langTags = source.readString8();
int variant = source.readInt();
- int varFamilyType = source.readInt();
- return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant,
- varFamilyType);
+ return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
}
@Override
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 6e45fea..f3e0ea7 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -96,3 +96,10 @@
description: "A feature flag for fixing the crash while delete text in insert mode."
bug: "314254153"
}
+
+flag {
+ name: "insert_mode_not_update_selection"
+ namespace: "text"
+ description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
+ bug: "300850862"
+}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index c0ceb9ea..9ecb4cb 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -217,7 +217,6 @@
* @see Log#wtf(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
}
@@ -263,7 +262,6 @@
*
* @see Log#wtf(String, Throwable)
*/
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
}
@@ -284,7 +282,6 @@
* @see Log#wtf(String, String, Throwable)
*/
@UnsupportedAppUsage
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
}
diff --git a/core/java/android/view/HdrRenderState.java b/core/java/android/view/HdrRenderState.java
index 2fbbf48..eadc507 100644
--- a/core/java/android/view/HdrRenderState.java
+++ b/core/java/android/view/HdrRenderState.java
@@ -31,9 +31,11 @@
private final ViewRootImpl mViewRoot;
+ private boolean mIsHdrEnabled = false;
private boolean mIsListenerRegistered = false;
private boolean mUpdateHdrSdrRatioInfo = false;
private float mDesiredHdrSdrRatio = 1f;
+ private float mTargetDesiredHdrSdrRatio = 1f;
private float mTargetHdrSdrRatio = 1f;
private float mRenderHdrSdrRatio = 1f;
private float mPreviousRenderRatio = 1f;
@@ -50,7 +52,7 @@
}
boolean isHdrEnabled() {
- return mDesiredHdrSdrRatio >= 1.01f;
+ return mIsHdrEnabled;
}
void stopListening() {
@@ -75,9 +77,7 @@
final float maxStep = timeDelta * TRANSITION_PER_MS;
mLastUpdateMillis = frameTimeMillis;
if (hasUpdate && FLAG_ANIMATE_ENABLED) {
- if (mTargetHdrSdrRatio == 1.0f) {
- mPreviousRenderRatio = mTargetHdrSdrRatio;
- } else {
+ if (isHdrEnabled()) {
float delta = mTargetHdrSdrRatio - mPreviousRenderRatio;
if (delta > maxStep) {
mRenderHdrSdrRatio = mPreviousRenderRatio + maxStep;
@@ -85,6 +85,19 @@
mViewRoot.invalidate();
}
mPreviousRenderRatio = mRenderHdrSdrRatio;
+
+ if (mTargetDesiredHdrSdrRatio < mDesiredHdrSdrRatio) {
+ mDesiredHdrSdrRatio = Math.max(mTargetDesiredHdrSdrRatio,
+ mDesiredHdrSdrRatio - maxStep);
+ if (mDesiredHdrSdrRatio != mTargetDesiredHdrSdrRatio) {
+ mUpdateHdrSdrRatioInfo = true;
+ mViewRoot.invalidate();
+ }
+ }
+
+ } else {
+ mPreviousRenderRatio = mTargetHdrSdrRatio;
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
}
}
return hasUpdate;
@@ -99,15 +112,23 @@
}
void forceUpdateHdrSdrRatio() {
- mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mViewRoot.mDisplay.getHdrSdrRatio());
+ if (isHdrEnabled()) {
+ mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio,
+ mViewRoot.mDisplay.getHdrSdrRatio());
+ } else {
+ mTargetHdrSdrRatio = 1.0f;
+ }
mUpdateHdrSdrRatioInfo = true;
}
- void setDesiredHdrSdrRatio(float desiredRatio) {
+ void setDesiredHdrSdrRatio(boolean isHdrEnabled, float desiredRatio) {
+ mIsHdrEnabled = isHdrEnabled;
mLastUpdateMillis = SystemClock.uptimeMillis();
- // TODO: When decreasing the desired ratio we need to animate it downwards
- if (desiredRatio != mDesiredHdrSdrRatio) {
- mDesiredHdrSdrRatio = desiredRatio;
+ if (desiredRatio != mTargetDesiredHdrSdrRatio) {
+ mTargetDesiredHdrSdrRatio = desiredRatio;
+ if (mTargetDesiredHdrSdrRatio > mDesiredHdrSdrRatio || !FLAG_ANIMATE_ENABLED) {
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
+ }
forceUpdateHdrSdrRatio();
mViewRoot.invalidate();
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index c6601e8d..1ee9509 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -19,6 +19,7 @@
import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
import static android.view.Display.INVALID_DISPLAY;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -31,6 +32,8 @@
import android.util.SparseIntArray;
import android.view.KeyCharacterMap.KeyData;
+import com.android.hardware.input.Flags;
+
import java.util.concurrent.TimeUnit;
/**
@@ -384,7 +387,13 @@
public static final int KEYCODE_META_RIGHT = 118;
/** Key code constant: Function modifier key. */
public static final int KEYCODE_FUNCTION = 119;
- /** Key code constant: System Request / Print Screen key. */
+ /**
+ * Key code constant: System Request / Print Screen key.
+ *
+ * This key is sent to the app first and only if the app doesn't handle it, the framework
+ * handles it (to take a screenshot), unlike {@code KEYCODE_TAKE_SCREENSHOT} which is
+ * fully handled by the framework.
+ */
public static final int KEYCODE_SYSRQ = 120;
/** Key code constant: Break / Pause key. */
public static final int KEYCODE_BREAK = 121;
@@ -921,14 +930,25 @@
* User customizable key #4.
*/
public static final int KEYCODE_MACRO_4 = 316;
-
+ /** Key code constant: To open emoji picker */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_EMOJI_PICKER = 317;
+ /**
+ * Key code constant: To take a screenshot
+ *
+ * This key is fully handled by the framework and will not be sent to the foreground app,
+ * unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app
+ * doesn't handle it, the framework handles it (to take a screenshot).
+ */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_SCREENSHOT = 318;
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_MACRO_4;
+ public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 27af300..db2efaa 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.content.Context;
+import com.android.internal.annotations.VisibleForTesting;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -57,11 +59,21 @@
* @param context The context for the predictions
*/
public MotionPredictor(@NonNull Context context) {
- mIsPredictionEnabled = context.getResources().getBoolean(
- com.android.internal.R.bool.config_enableMotionPrediction);
- final int offsetNanos = context.getResources().getInteger(
- com.android.internal.R.integer.config_motionPredictionOffsetNanos);
- mPtr = nativeInitialize(offsetNanos);
+ this(
+ context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableMotionPrediction),
+ context.getResources().getInteger(
+ com.android.internal.R.integer.config_motionPredictionOffsetNanos));
+ }
+
+ /**
+ * Internal constructor for testing.
+ * @hide
+ */
+ @VisibleForTesting
+ public MotionPredictor(boolean isPredictionEnabled, int motionPredictionOffsetNanos) {
+ mIsPredictionEnabled = isPredictionEnabled;
+ mPtr = nativeInitialize(motionPredictionOffsetNanos);
RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7bc832e..c18aeee 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -399,6 +399,8 @@
@Nullable
private ContentObserver mForceInvertObserver;
+ private static final int INVALID_VALUE = Integer.MIN_VALUE;
+ private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -1604,6 +1606,23 @@
}
}
+ private boolean isForceInvertEnabled() {
+ if (mForceInvertEnabled == INVALID_VALUE) {
+ reloadForceInvertEnabled();
+ }
+ return mForceInvertEnabled == 1;
+ }
+
+ private void reloadForceInvertEnabled() {
+ if (forceInvertColor()) {
+ mForceInvertEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0,
+ UserHandle.myUserId());
+ }
+ }
+
/**
* Register any kind of listeners if setView was success.
*/
@@ -1630,6 +1649,7 @@
mForceInvertObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
+ reloadForceInvertEnabled();
updateForceDarkMode();
}
};
@@ -1850,16 +1870,11 @@
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
- boolean isForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId()) == 1;
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
// for dark mode in configuration.uiMode. Instead, we assume that the force invert
// setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled) {
+ if (isForceInvertEnabled()) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
@@ -5809,9 +5824,11 @@
if (mAttachInfo.mThreadedRenderer == null) {
return;
}
- if ((colorMode == ActivityInfo.COLOR_MODE_HDR || colorMode == ActivityInfo.COLOR_MODE_HDR10)
- && !mDisplay.isHdrSdrRatioAvailable()) {
+ boolean isHdr = colorMode == ActivityInfo.COLOR_MODE_HDR
+ || colorMode == ActivityInfo.COLOR_MODE_HDR10;
+ if (isHdr && !mDisplay.isHdrSdrRatioAvailable()) {
colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+ isHdr = false;
}
// TODO: Centralize this sanitization? Why do we let setting bad modes?
// Alternatively, can we just let HWUI figure it out? Do we need to care here?
@@ -5824,7 +5841,7 @@
desiredRatio = automaticRatio;
}
- mHdrRenderState.setDesiredHdrSdrRatio(desiredRatio);
+ mHdrRenderState.setDesiredHdrSdrRatio(isHdr, desiredRatio);
}
@Override
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
new file mode 100644
index 0000000..6938b29
--- /dev/null
+++ b/core/java/android/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+ name: "update_service_ipc_wrapper"
+ namespace: "webview"
+ description: "New API: proper wrapper for IWebViewUpdateService"
+ bug: "319292658"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0654add..2433bd8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -7597,14 +7597,6 @@
}
/**
- * Append additional instructions to this {@link DrawInstructions} object.
- */
- @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
- public void appendInstructions(@NonNull final byte[] instructions) {
- mInstructions.add(instructions);
- }
-
- /**
* Builder class for {@link DrawInstructions} objects.
*/
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e812f85..90d5140 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -243,6 +243,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import libcore.util.EmptyArray;
@@ -539,7 +540,6 @@
// System wide time for last cut, copy or text changed action.
static long sLastCutCopyOrTextChangedTime;
-
private ColorStateList mTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
@@ -2857,7 +2857,38 @@
}
if (updateText) {
- setText(mText);
+ if (Flags.insertModeNotUpdateSelection()) {
+ // Update the transformation text.
+ if (mTransformation == null) {
+ mTransformed = mText;
+ } else {
+ mTransformed = mTransformation.getTransformation(mText, this);
+ }
+ if (mTransformed == null) {
+ // Should not happen if the transformation method follows the non-null
+ // postcondition.
+ mTransformed = "";
+ }
+ final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
+
+ // If the mText is a Spannable and the new TransformationMethod needs to listen to
+ // its updates, apply the watcher on it.
+ if (mTransformation != null && mText instanceof Spannable
+ && (!mAllowTransformationLengthChange || isOffsetMapping)) {
+ Spannable sp = (Spannable) mText;
+ final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
+ sp.setSpan(mTransformation, 0, mText.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE
+ | (priority << Spanned.SPAN_PRIORITY_SHIFT));
+ }
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ } else {
+ setText(mText);
+ }
}
if (hasPasswordTransformationMethod()) {
diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
index ae6ad32..94bbd72 100644
--- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
@@ -19,5 +19,5 @@
// Iterface to observe op active changes
oneway interface IAppOpsActiveCallback {
void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, int attributionFlags, int attributionChainId);
+ int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 024ff66..3a9525c 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
// This interface is also used by native code, so must
// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName);
+ void opChanged(int op, int uid, String packageName, String persistentDeviceId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
index f3759e0..123f4f4 100644
--- a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
@@ -18,5 +18,6 @@
// Iterface to observe op note/checks of ops
oneway interface IAppOpsNotedCallback {
- void opNoted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+ void opNoted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode);
}
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 06640cb..fdae37a 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,6 +18,6 @@
// Iterface to observe op starts
oneway interface IAppOpsStartedCallback {
- void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
- int startedType, int attributionFlags, int attributionChainId);
+ void opStarted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode, int startedType, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/os/AndroidPrintStream.java b/core/java/com/android/internal/os/AndroidPrintStream.java
index a6e41ff..bb388bb 100644
--- a/core/java/com/android/internal/os/AndroidPrintStream.java
+++ b/core/java/com/android/internal/os/AndroidPrintStream.java
@@ -24,6 +24,7 @@
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
class AndroidPrintStream extends LoggingPrintStream {
private final int priority;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..cdac097 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -39,6 +39,7 @@
import libcore.content.type.MimeMap;
+import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -50,6 +51,7 @@
* public consumption.
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class RuntimeInit {
final static String TAG = "AndroidRuntime";
final static boolean DEBUG = false;
@@ -67,7 +69,15 @@
private static volatile ApplicationWtfHandler sDefaultApplicationWtfHandler;
+ /**
+ * Stored values of System.out and System.err before they've been replaced by
+ * redirectLogStreams(). Kept open here for other Ravenwood internals to use.
+ */
+ public static PrintStream sOut$ravenwood;
+ public static PrintStream sErr$ravenwood;
+
private static final native void nativeFinishInit();
+
private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
private static int Clog_e(String tag, String msg, Throwable tr) {
@@ -385,6 +395,7 @@
/**
* Redirect System.out and System.err to the Android log.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
@@ -392,6 +403,17 @@
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
+ public static void redirectLogStreams$ravenwood() {
+ if (sOut$ravenwood == null) {
+ sOut$ravenwood = System.out;
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ }
+ if (sErr$ravenwood == null) {
+ sErr$ravenwood = System.err;
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ }
+ }
+
/**
* Report a serious error in the current process. May or may not cause
* the process to terminate (depends on system settings).
@@ -399,6 +421,7 @@
* @param tag to record with the error
* @param t exception describing the error site and conditions
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void wtf(String tag, Throwable t, boolean system) {
try {
boolean exit = false;
@@ -436,6 +459,11 @@
}
}
+ public static void wtf$ravenwood(String tag, Throwable t, boolean system) {
+ // We've already emitted to logs, so there's nothing more to do here,
+ // as we don't have a DropBox pipeline configured
+ }
+
/**
* Set the default {@link ApplicationWtfHandler}, in case the ActivityManager is not ready yet.
*/
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 4e3b64c..b4f9ee3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2583,7 +2583,7 @@
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
Color.TRANSPARENT);
}
- if (!targetPreQ && !mEdgeToEdgeEnforced) {
+ if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3966,9 +3966,6 @@
@Override
public void setStatusBarContrastEnforced(boolean ensureContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureStatusBarContrastWhenTransparent = ensureContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3982,9 +3979,6 @@
@Override
public void setNavigationBarContrastEnforced(boolean enforceContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 5351c6d..ed43b81 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -151,8 +151,17 @@
* @param hidesStatusBar whether it is being hidden
*/
void setTopAppHidesStatusBar(boolean hidesStatusBar);
-
+ /**
+ * Add a tile to the Quick Settings Panel to the first item in the QS Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
void addQsTile(in ComponentName tile);
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(in ComponentName tile, boolean end);
void remQsTile(in ComponentName tile);
void setQsTiles(in String[] tiles);
void clickQsTile(in ComponentName tile);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 656cc3e..3fc1683 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -284,7 +284,6 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
- "libperfetto_client_experimental",
],
shared_libs: [
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3413ede..969e47b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -25,6 +25,7 @@
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
#include <jni.h>
+#include <media/AidlConversion.h>
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
@@ -66,6 +67,11 @@
jmethodID toArray;
} gArrayListMethods;
+static jclass gIntArrayClass;
+static struct {
+ jmethodID add;
+} gIntArrayMethods;
+
static jclass gBooleanClass;
static jmethodID gBooleanCstor;
@@ -1607,6 +1613,48 @@
return jStatus;
}
+// From AudioDeviceInfo
+static const int GET_DEVICES_INPUTS = 0x0001;
+static const int GET_DEVICES_OUTPUTS = 0x0002;
+
+static int android_media_AudioSystem_getSupportedDeviceTypes(JNIEnv *env, jobject clazz,
+ jint direction, jobject jDeviceTypes) {
+ if (jDeviceTypes == NULL) {
+ ALOGE("%s NULL Device Types IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jDeviceTypes, gIntArrayClass)) {
+ ALOGE("%s not an IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ // Convert AudioManager.GET_DEVICES_ flags to AUDIO_PORT_ROLE_ constants
+ audio_port_role_t role;
+ if (direction == GET_DEVICES_INPUTS) {
+ role = AUDIO_PORT_ROLE_SOURCE;
+ } else if (direction == GET_DEVICES_OUTPUTS) {
+ role = AUDIO_PORT_ROLE_SINK;
+ } else {
+ ALOGE("%s invalid direction : 0x%X", __func__, direction);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ std::vector<media::AudioPortFw> deviceList;
+ AudioSystem::listDeclaredDevicePorts(static_cast<media::AudioPortRole>(role), &deviceList);
+
+ // Walk the device list
+ for (const auto &device : deviceList) {
+ ConversionResult<audio_port_v7> result = aidl2legacy_AudioPortFw_audio_port_v7(device);
+
+ struct audio_port_v7 port = VALUE_OR_RETURN_STATUS(result);
+ assert(port.type == AUDIO_PORT_TYPE_DEVICE);
+
+ env->CallVoidMethod(jDeviceTypes, gIntArrayMethods.add, port.ext.device.type);
+ }
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
static int
android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
@@ -3184,6 +3232,8 @@
android_media_AudioSystem_setAudioFlingerBinder),
MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
android_media_AudioSystem_listAudioPorts),
+ MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I",
+ android_media_AudioSystem_getSupportedDeviceTypes),
MAKE_JNI_NATIVE_METHOD("createAudioPatch",
"([Landroid/media/AudioPatch;[Landroid/media/"
"AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
@@ -3325,6 +3375,10 @@
gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
gArrayListMethods.toArray = GetMethodIDOrDie(env, arrayListClass, "toArray", "()[Ljava/lang/Object;");
+ jclass intArrayClass = FindClassOrDie(env, "android/util/IntArray");
+ gIntArrayClass = MakeGlobalRefOrDie(env, intArrayClass);
+ gIntArrayMethods.add = GetMethodIDOrDie(env, gIntArrayClass, "add", "(I)V");
+
jclass booleanClass = FindClassOrDie(env, "java/lang/Boolean");
gBooleanClass = MakeGlobalRefOrDie(env, booleanClass);
gBooleanCstor = GetMethodIDOrDie(env, booleanClass, "<init>", "(Z)V");
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index d710698..16c3ca9 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 4ddf1d8..61a7654 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -23,10 +23,10 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
+#include <map>
#include <sstream>
#include <thread>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
index e659bf1..3fbd5b3 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index d577655..ebb5259 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
index ce72f58..f8c63c8 100644
--- a/core/jni/android_tracing_PerfettoProducer.cpp
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d4a7462..d11166f 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -203,8 +203,10 @@
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- std::unique_ptr<InputChannel> inputChannel = std::make_unique<InputChannel>();
- inputChannel->readFromParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ parcelableChannel.readFromParcel(parcel);
+ std::unique_ptr<InputChannel> inputChannel =
+ InputChannel::create(std::move(parcelableChannel));
NativeInputChannel* nativeInputChannel =
new NativeInputChannel(std::move(inputChannel));
return reinterpret_cast<jlong>(nativeInputChannel);
@@ -228,7 +230,9 @@
return;
}
parcel->writeInt32(1); // initialized
- nativeInputChannel->getInputChannel()->writeToParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ nativeInputChannel->getInputChannel()->copyTo(parcelableChannel);
+ parcelableChannel.writeToParcel(parcel);
}
static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj, jlong channel) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f7d8152..f93b306 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -189,11 +189,11 @@
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
- auto&& fd = mInputConsumer.getChannel()->getFd();
+ const int fd = mInputConsumer.getChannel()->getFd();
if (events) {
- mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
+ mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
- mMessageQueue->getLooper()->removeFd(fd.get());
+ mMessageQueue->getLooper()->removeFd(fd);
}
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 6bdf821..323f7b6 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
}
status_t NativeInputEventSender::initialize() {
- auto&& receiveFd = mInputPublisher.getChannel()->getFd();
- mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
+ const int receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a854e36..381580b 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -13,7 +13,7 @@
jjaggi@google.com
kwekua@google.com
roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 48243f2..5fc2a59 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -207,6 +207,7 @@
optional SettingProto pointer_speed = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto right_click_zone = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Touchpad touchpad = 36;
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index fe12f6e..f3acab0 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2024 The Android Open Source Project
+Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,103 +20,179 @@
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="0">
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="56.22"
- android:startY="256"
- android:endX="456.22"
- android:endY="256"
+ android:startX="256"
+ android:startY="21.81"
+ android:endX="256"
+ android:endY="350.42"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
<path
- android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="55"
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
+ android:strokeWidth="56.561"
android:fillColor="#00000000"
- android:strokeColor="#f86733"/>
+ android:strokeColor="#f86734"/>
+ <path
+ android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:fillColor="#fff"/>
</vector>
+
diff --git a/core/res/res/drawable/ic_satellite_alt_24px.xml b/core/res/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 0000000..f9ca7dc
--- /dev/null
+++ b/core/res/res/drawable/ic_satellite_alt_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ebb0d34..238f242 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -283,6 +283,20 @@
when there's no network connection. If the scan doesn't timeout, use zero -->
<integer name="config_radioScanningTimeout">0</integer>
+ <!-- The duration (in milliseconds) that the vibrator control service will wait for new
+ vibration params. -->
+ <integer name="config_requestVibrationParamsTimeout">50</integer>
+
+ <!-- Array containing the usages that should request vibration params before they are played.
+ These usages don't have strong latency requirements, e.g. ringtone and notification, and
+ can be slightly delayed. -->
+ <integer-array name="config_requestVibrationParamsForUsages">
+ <item>17</item> <!-- USAGE_ALARM -->
+ <item>33</item> <!-- USAGE_RINGTONE -->
+ <item>49</item> <!-- USAGE_NOTIFICATION -->
+ <item>65</item> <!-- USAGE_COMMUNICATION_REQUEST -->
+ </integer-array>
+
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
@@ -3327,9 +3341,27 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
- <!-- Name of the dialog that is used to get or save an app credential -->
+ <!-- Name of the default framework dialog that is used to get or save an app credential.
+
+ This UI should be always launch-able and is used as a fallback when an oem replacement activity
+ (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
<string name="config_credentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+ <!-- Whether to allow the credential selector activity to be replaced by an activity at
+ run-time (restricted to the privileged activity specified by
+ config_credentialSelectorActivityName).
+
+ When disabled, the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
+ <!-- Fully qualified activity name providing the credential selector UI, that serves the
+ CredentialManager APIs.
+
+ Used only when config_enableOemCredentialManagerDialogComponent is true.
+
+ If the activity specified cannot be found or launched, then the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
<!-- Name of the broadcast receiver that is used to receive provider change events -->
<string name="config_credentialManagerReceiverComponent" translatable="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 558bae7..dd6e79e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6386,4 +6386,14 @@
<string name="redacted_notification_message"></string>
<!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] -->
<string name="redacted_notification_action_title"></string>
+
+ <!-- Satellite related messages -->
+ <!-- Notification title when satellite service is connected. -->
+ <string name="satellite_notification_title">Auto connected to satellite</string>
+ <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Invoke "What to expect" dialog of messaging application -->
+ <string name="satellite_notification_open_message">Open Messages</string>
+ <!-- Invoke Satellite setting activity of Settings -->
+ <string name="satellite_notification_how_it_works">How it works</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc2f74b..699c8ac 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2080,6 +2080,8 @@
<java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
<java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
+ <java-symbol type="integer" name="config_requestVibrationParamsTimeout" />
+ <java-symbol type="array" name="config_requestVibrationParamsForUsages" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
<java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
@@ -2281,6 +2283,8 @@
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
<java-symbol type="string" name="config_credentialManagerDialogComponent" />
+ <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
@@ -5325,4 +5329,11 @@
<java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
<java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
+
+ <!-- System notification for satellite service -->
+ <java-symbol type="string" name="satellite_notification_title" />
+ <java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_open_message" />
+ <java-symbol type="string" name="satellite_notification_how_it_works" />
+ <java-symbol type="drawable" name="ic_satellite_alt_24px" />
</resources>
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 9e8e2d6..cd5deb6 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -20,24 +20,33 @@
import static org.mockito.Mockito.when;
+import android.app.IBackupAgent;
import android.app.backup.BackupAgent.IncludeExcludeRules;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
+import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -49,9 +58,12 @@
private static final UserHandle USER_HANDLE = new UserHandle(15);
private static final String DATA_TYPE_BACKED_UP = "test data type";
+ @Mock IBackupManager mIBackupManager;
@Mock FullBackup.BackupScheme mBackupScheme;
+ @Mock Context mContext;
- private BackupAgent mBackupAgent;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -67,11 +79,11 @@
excludePaths.add(path);
IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
- mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
+ BackupAgent backupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
- IncludeExcludeRules rules = mBackupAgent.getIncludeExcludeRules(mBackupScheme);
+ IncludeExcludeRules rules = backupAgent.getIncludeExcludeRules(mBackupScheme);
assertThat(rules).isEqualTo(expectedRules);
}
@@ -137,6 +149,31 @@
0).getSuccessCount()).isEqualTo(1);
}
+ @Test
+ public void doRestoreFile_agentOverrideIgnoresFile_consumesAllBytesInBuffer() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_CLEAR_PIPE_AFTER_RESTORE_FILE);
+ BackupAgent agent = new TestRestoreIgnoringFullBackupAgent();
+ agent.attach(mContext);
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
+ IBackupAgent agentBinder = (IBackupAgent) agent.onBind();
+
+ ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();
+ FileOutputStream writeSide = new FileOutputStream(
+ pipes[1].getFileDescriptor());
+ writeSide.write("Hello".getBytes(StandardCharsets.UTF_8));
+
+ agentBinder.doRestoreFile(pipes[0], /* length= */ 5, BackupAgent.TYPE_FILE,
+ FullBackup.FILES_TREE_TOKEN, /* path= */ "hello_file", /* mode= */
+ 0666, /* mtime= */ 12345, /* token= */ 6789, mIBackupManager);
+
+ try (FileInputStream in = new FileInputStream(pipes[0].getFileDescriptor())) {
+ assertThat(in.available()).isEqualTo(0);
+ } finally {
+ pipes[0].close();
+ pipes[1].close();
+ }
+ }
+
private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, backupDestination);
@@ -144,7 +181,6 @@
}
private static class TestFullBackupAgent extends BackupAgent {
-
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
@@ -162,4 +198,14 @@
// Left empty as this is a full backup agent.
}
}
+
+ private static class TestRestoreIgnoringFullBackupAgent extends TestFullBackupAgent {
+
+ @Override
+ protected void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // Ignore the file and don't consume any data.
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 4dd5889..5f96c17 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -22,14 +22,18 @@
import static android.text.FontConfig.FontFamily.VARIANT_COMPACT;
import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT;
import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import android.graphics.fonts.FontCustomizationParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontStyle;
+import android.graphics.fonts.SystemFonts;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.Xml;
@@ -64,9 +68,9 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(
Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -82,12 +86,11 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", "serif")),
- LocaleList.forLanguageTags("en"), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", "serif", FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -103,9 +106,8 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_COMPACT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -121,9 +123,8 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_ELEGANT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -140,13 +141,15 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("normal.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("weight.ttf"), null, "test",
- new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("italic.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -168,12 +171,13 @@
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 100.0,'wght' 200.0", null),
+ 0, "'wdth' 100.0,'wght' 200.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 400.0,'wght' 700.0", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 0, "'wdth' 400.0,'wght' 700.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -190,12 +194,11 @@
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 1, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -211,11 +214,12 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "foo",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -385,14 +389,81 @@
public void varFamilyType() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<familyset>"
- + " <family name='sans-serif' varFamilyType='1'>"
- + " <font>test.ttf</font>"
+ + " <family name='sans-serif'>"
+ + " <font supportedAxes='wght'>test.ttf</font>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ " </family>"
+ "</familyset>";
FontConfig config = readFamilies(xml, true /* include non-existing font files */);
List<FontConfig.FontFamily> families = config.getFontFamilies();
assertThat(families.size()).isEqualTo(1); // legacy one should be ignored.
- assertThat(families.get(0).getVariableFontFamilyType()).isEqualTo(1);
+ assertThat(families.get(0).getFontList().get(0).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT);
+ assertThat(families.get(0).getFontList().get(1).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ assertThat(families.get(0).getFontList().get(2).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT | FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font style='italic' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(5);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(0), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(2), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(3), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(4), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve_ForName() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font fallbackFor='serif' supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(2);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), "serif"))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
}
private FontConfig readFamilies(String xml, boolean allowNonExisting)
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
index f9966a1..15caac9 100644
--- a/core/tests/coretests/src/android/util/LogTest.java
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -37,6 +37,13 @@
private static final String LOG_TAG = "LogTest";
@Test
+ public void testWtf() {
+ Log.wtf(LOG_TAG, "Message");
+ Log.wtf(LOG_TAG, "Message", new Throwable("Throwable"));
+ Log.wtf(LOG_TAG, new Throwable("Throwable"));
+ }
+
+ @Test
@Ignore
public void testIsLoggable() {
// First clear any SystemProperty setting for our test key.
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 543d73b..5862711 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -450,13 +450,8 @@
}
private RemoteViews.DrawInstructions getDrawInstructions() {
- final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
- final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
- final RemoteViews.DrawInstructions drawInstructions =
- new RemoteViews.DrawInstructions.Builder(
- Collections.singletonList(first)).build();
- drawInstructions.appendInstructions(second);
- return drawInstructions;
+ final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
+ return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
}
private RemoteViews createViewChained(int depth, String... texts) {
diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java
index 6f761e3..738c668 100644
--- a/core/tests/utiltests/src/android/util/SlogTest.java
+++ b/core/tests/utiltests/src/android/util/SlogTest.java
@@ -44,4 +44,11 @@
Slog.w(TAG, MSG, THROWABLE);
Slog.e(TAG, MSG, THROWABLE);
}
+
+ @Test
+ public void testWtf() {
+ Slog.wtf(TAG, MSG);
+ Slog.wtf(TAG, MSG, THROWABLE);
+ Slog.wtf(TAG, THROWABLE);
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba..245f216 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,3 +1,5 @@
+include /PACKAGE_MANAGER_OWNERS
+
alanstokes@google.com
cbrubaker@google.com
hackbod@android.com
@@ -6,11 +8,6 @@
jsharkey@android.com
jsharkey@google.com
lorenzo@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
-patb@google.com
yamasani@google.com
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 1bd182f..15ea15a 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -865,27 +926,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index 75bc74e..c1ca67e 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -707,123 +768,36 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="2" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="3" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="ja">
- <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="0" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -840,41 +814,12 @@
</font>
</family>
<family lang="ko">
- <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="1" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -997,27 +942,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d1aceaf..b33a5d2 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -18,6 +18,7 @@
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -30,6 +31,8 @@
import android.os.Build;
import android.text.TextShaper;
+import com.android.graphics.hwui.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -766,6 +769,21 @@
}
/**
+ * Preconcat the current matrix with the specified matrix. If the specified
+ * matrix is null, this method does nothing. If the canvas's matrix is changed in the z-axis
+ * through this function, the deprecated {@link #getMatrix()} method will return a 3x3 with
+ * z-axis info stripped away.
+ *
+ * @param m The 4x4 matrix to preconcatenate with the current matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void concat44(@Nullable Matrix44 m) {
+ if (m != null) {
+ nConcat(mNativeCanvasWrapper, m.mBackingArray);
+ }
+ }
+
+ /**
* Completely replace the current matrix with the specified matrix. If the
* matrix parameter is null, then the current matrix is reset to identity.
*
@@ -1444,6 +1462,8 @@
private static native void nSkew(long canvasHandle, float sx, float sy);
@CriticalNative
private static native void nConcat(long nativeCanvas, long nativeMatrix);
+ @FastNative
+ private static native void nConcat(long nativeCanvas, float[] mat);
@CriticalNative
private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
@CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 52b0b95..17c2dd9 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,10 +16,6 @@
package android.graphics;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
@@ -32,7 +28,6 @@
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -65,6 +60,7 @@
private static final String VARIANT_ELEGANT = "elegant";
// XML constants for Font.
+ public static final String ATTR_SUPPORTED_AXES = "supportedAxes";
public static final String ATTR_INDEX = "index";
public static final String ATTR_WEIGHT = "weight";
public static final String ATTR_POSTSCRIPT_NAME = "postScriptName";
@@ -78,6 +74,10 @@
public static final String ATTR_TAG = "tag";
public static final String ATTR_STYLEVALUE = "stylevalue";
+ // The tag string for variable font type resolution.
+ private static final String TAG_WGHT = "wght";
+ private static final String TAG_ITAL = "ital";
+
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
@@ -263,7 +263,6 @@
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
- final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType");
final List<FontConfig.Font> fonts = new ArrayList<>();
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -286,45 +285,12 @@
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
- int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- if (varFamilyTypeStr != null) {
- varFamilyType = Integer.parseInt(varFamilyTypeStr);
- if (varFamilyType <= -1 || varFamilyType > 3) {
- Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr);
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
-
- // validation but don't read font content for performance reasons.
- switch (varFamilyType) {
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are"
- + " included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support both ital and wght axes, but two or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT:
- if (fonts.size() != 2) {
- Log.e(TAG, "Error: two fonts that support wght axis, but one or three or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- }
- }
boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant,
- varFamilyType);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
}
private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
@@ -407,6 +373,7 @@
boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE));
String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR);
String postScriptName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME);
+ final String supportedAxes = parser.getAttributeValue(null, ATTR_SUPPORTED_AXES);
StringBuilder filename = new StringBuilder();
while (keepReading(parser)) {
if (parser.getEventType() == XmlPullParser.TEXT) {
@@ -422,6 +389,18 @@
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+ int varTypeAxes = 0;
+ if (supportedAxes != null) {
+ for (String tag : supportedAxes.split(",")) {
+ String strippedTag = tag.strip();
+ if (strippedTag.equals(TAG_WGHT)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_WGHT;
+ } else if (strippedTag.equals(TAG_ITAL)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_ITAL;
+ }
+ }
+ }
+
if (postScriptName == null) {
// If post script name was not provided, assume the file name is same to PostScript
// name.
@@ -462,7 +441,8 @@
),
index,
varSettings,
- fallbackFor);
+ fallbackFor,
+ varTypeAxes);
}
private static String findUpdatedFontFile(String psName,
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
new file mode 100644
index 0000000..7cc0eb7
--- /dev/null
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -0,0 +1,472 @@
+/*
+ * 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 android.graphics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.Arrays;
+
+/**
+ * The Matrix44 class holds a 4x4 matrix for transforming coordinates. It is similar to
+ * {@link Matrix}, and should be used when you want to manipulate the canvas in 3D. Values are kept
+ * in row-major order. The values and operations are treated as column vectors.
+ */
+@FlaggedApi(Flags.FLAG_MATRIX_44)
+public class Matrix44 {
+ final float[] mBackingArray;
+ /**
+ * The default Matrix44 constructor will instantiate an identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44() {
+ mBackingArray = new float[]{1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ }
+
+ /**
+ * Creates and returns a Matrix44 by taking the 3x3 Matrix and placing it on the 0 of the z-axis
+ * by setting row {@code 2} and column {@code 2} to the identity as seen in the following
+ * operation:
+ * <pre class="prettyprint">
+ * [ a b c ] [ a b 0 c ]
+ * [ d e f ] -> [ d e 0 f ]
+ * [ g h i ] [ 0 0 1 0 ]
+ * [ g h 0 i ]
+ * </pre>
+ *
+ * @param mat A 3x3 Matrix to be converted (original Matrix will not be changed)
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44(@NonNull Matrix mat) {
+ float[] m = new float[9];
+ mat.getValues(m);
+ mBackingArray = new float[]{m[0], m[1], 0.0f, m[2],
+ m[3], m[4], 0.0f, m[5],
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ m[6], m[7], 0.0f, m[8]};
+ }
+
+ /**
+ * Copies matrix values into the provided array in row-major order.
+ *
+ * @param dst The float array where values will be copied, must be of length 16
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void getValues(@NonNull float [] dst) {
+ if (dst.length == 16) {
+ System.arraycopy(mBackingArray, 0, dst, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Dst array must be of length 16");
+ }
+ }
+
+ /**
+ * Replaces the Matrix's values with the values in the provided array.
+ *
+ * @param src A float array of length 16. Floats are treated in row-major order
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void setValues(@NonNull float[] src) {
+ if (src.length == 16) {
+ System.arraycopy(src, 0, mBackingArray, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Src array must be of length 16");
+ }
+ }
+
+ /**
+ * Gets the value at the matrix's row and column.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to get
+ * @param col An integer from 0 to 4 indicating the column of the value to get
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public float get(int row, int col) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ return mBackingArray[row * 4 + col];
+ }
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+
+ /**
+ * Sets the value at the matrix's row and column to the provided value.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to change
+ * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param val The value the element at the specified index will be set to
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void set(int row, int col, float val) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ mBackingArray[row * 4 + col] = val;
+ } else {
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+ }
+
+ /**
+ * Sets the Matrix44 to the identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void reset() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ mBackingArray[i] = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ }
+ }
+
+ /**
+ * Inverts the Matrix44, then return true if successful, false if unable to invert.
+ *
+ * @return {@code true} on success, {@code false} otherwise
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean invert() {
+ float a00 = mBackingArray[0];
+ float a01 = mBackingArray[1];
+ float a02 = mBackingArray[2];
+ float a03 = mBackingArray[3];
+ float a10 = mBackingArray[4];
+ float a11 = mBackingArray[5];
+ float a12 = mBackingArray[6];
+ float a13 = mBackingArray[7];
+ float a20 = mBackingArray[8];
+ float a21 = mBackingArray[9];
+ float a22 = mBackingArray[10];
+ float a23 = mBackingArray[11];
+ float a30 = mBackingArray[12];
+ float a31 = mBackingArray[13];
+ float a32 = mBackingArray[14];
+ float a33 = mBackingArray[15];
+ float b00 = a00 * a11 - a01 * a10;
+ float b01 = a00 * a12 - a02 * a10;
+ float b02 = a00 * a13 - a03 * a10;
+ float b03 = a01 * a12 - a02 * a11;
+ float b04 = a01 * a13 - a03 * a11;
+ float b05 = a02 * a13 - a03 * a12;
+ float b06 = a20 * a31 - a21 * a30;
+ float b07 = a20 * a32 - a22 * a30;
+ float b08 = a20 * a33 - a23 * a30;
+ float b09 = a21 * a32 - a22 * a31;
+ float b10 = a21 * a33 - a23 * a31;
+ float b11 = a22 * a33 - a23 * a32;
+ float det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
+ if (det == 0.0f) {
+ return false;
+ }
+ float invDet = 1.0f / det;
+ mBackingArray[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet);
+ mBackingArray[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet);
+ mBackingArray[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet);
+ mBackingArray[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet);
+ mBackingArray[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet);
+ mBackingArray[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet);
+ mBackingArray[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet);
+ mBackingArray[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet);
+ mBackingArray[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet);
+ mBackingArray[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet);
+ mBackingArray[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet);
+ mBackingArray[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet);
+ mBackingArray[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet);
+ mBackingArray[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet);
+ mBackingArray[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet);
+ mBackingArray[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet);
+ return true;
+ }
+
+ /**
+ * Returns true if Matrix44 is equal to identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean isIdentity() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ float expected = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ if (expected != mBackingArray[i]) return false;
+ }
+ return true;
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(Matrix44 a, Matrix44 b, int row, int col) {
+ return (a.get(row, 0) * b.get(0, col))
+ + (a.get(row, 1) * b.get(1, col))
+ + (a.get(row, 2) * b.get(2, col))
+ + (a.get(row, 3) * b.get(3, col));
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(float r0, float r1, float r2, float r3,
+ float c0, float c1, float c2, float c3) {
+ return (r0 * c0) + (r1 * c1) + (r2 * c2) + (r3 * c3);
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ *
+ * @return An array of length 4 that represents the x, y, z, w (where w is perspective) value
+ * after multiplying x, y, z, 1 by the matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull float[] map(float x, float y, float z, float w) {
+ float[] dst = new float[4];
+ this.map(x, y, z, w, dst);
+ return dst;
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void map(float x, float y, float z, float w, @NonNull float[] dst) {
+ if (dst.length != 4) {
+ throw new IllegalArgumentException("Dst array must be of length 4");
+ }
+ dst[0] = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + w * mBackingArray[3];
+ dst[1] = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + w * mBackingArray[7];
+ dst[2] = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + w * mBackingArray[11];
+ dst[3] = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + w * mBackingArray[15];
+ }
+
+ /**
+ * Multiplies `this` matrix (A) and provided Matrix (B) in the order of A * B.
+ * The result is saved in `this` Matrix.
+ *
+ * @param b The second Matrix in the concatenation operation
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 concat(@NonNull Matrix44 b) {
+ float val00 = dot(this, b, 0, 0);
+ float val01 = dot(this, b, 0, 1);
+ float val02 = dot(this, b, 0, 2);
+ float val03 = dot(this, b, 0, 3);
+ float val10 = dot(this, b, 1, 0);
+ float val11 = dot(this, b, 1, 1);
+ float val12 = dot(this, b, 1, 2);
+ float val13 = dot(this, b, 1, 3);
+ float val20 = dot(this, b, 2, 0);
+ float val21 = dot(this, b, 2, 1);
+ float val22 = dot(this, b, 2, 2);
+ float val23 = dot(this, b, 2, 3);
+ float val30 = dot(this, b, 3, 0);
+ float val31 = dot(this, b, 3, 1);
+ float val32 = dot(this, b, 3, 2);
+ float val33 = dot(this, b, 3, 3);
+
+ mBackingArray[0] = val00;
+ mBackingArray[1] = val01;
+ mBackingArray[2] = val02;
+ mBackingArray[3] = val03;
+ mBackingArray[4] = val10;
+ mBackingArray[5] = val11;
+ mBackingArray[6] = val12;
+ mBackingArray[7] = val13;
+ mBackingArray[8] = val20;
+ mBackingArray[9] = val21;
+ mBackingArray[10] = val22;
+ mBackingArray[11] = val23;
+ mBackingArray[12] = val30;
+ mBackingArray[13] = val31;
+ mBackingArray[14] = val32;
+ mBackingArray[15] = val33;
+
+ return this;
+ }
+
+ /**
+ * Applies a rotation around a given axis, then returns self.
+ * {@code x}, {@code y}, {@code z} represent the axis by which to rotate around.
+ * For example, pass in {@code 1, 0, 0} to rotate around the x-axis.
+ * The axis provided will be normalized.
+ *
+ * @param deg Amount in degrees to rotate the matrix about the x-axis
+ * @param xComp X component of the rotation axis
+ * @param yComp Y component of the rotation axis
+ * @param zComp Z component of the rotation axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 rotate(float deg, float xComp, float yComp, float zComp) {
+ float sum = xComp + yComp + zComp;
+ float x = xComp / sum;
+ float y = yComp / sum;
+ float z = zComp / sum;
+
+ float c = (float) Math.cos(deg * Math.PI / 180.0f);
+ float s = (float) Math.sin(deg * Math.PI / 180.0f);
+ float t = 1 - c;
+
+ float rotVals00 = t * x * x + c;
+ float rotVals01 = t * x * y - s * z;
+ float rotVals02 = t * x * z + s * y;
+ float rotVals10 = t * x * y + s * z;
+ float rotVals11 = t * y * y + c;
+ float rotVals12 = t * y * z - s * x;
+ float rotVals20 = t * x * z - s * y;
+ float rotVals21 = t * y * z + s * x;
+ float rotVals22 = t * z * z + c;
+
+ float v00 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v01 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v02 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v03 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ 0, 0, 0, 1);
+ float v10 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v11 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v12 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v13 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ 0, 0, 0, 1);
+ float v20 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v21 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v22 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v23 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ 0, 0, 0, 1);
+ float v30 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v31 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v32 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v33 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ 0, 0, 0, 1);
+
+ mBackingArray[0] = v00;
+ mBackingArray[1] = v01;
+ mBackingArray[2] = v02;
+ mBackingArray[3] = v03;
+ mBackingArray[4] = v10;
+ mBackingArray[5] = v11;
+ mBackingArray[6] = v12;
+ mBackingArray[7] = v13;
+ mBackingArray[8] = v20;
+ mBackingArray[9] = v21;
+ mBackingArray[10] = v22;
+ mBackingArray[11] = v23;
+ mBackingArray[12] = v30;
+ mBackingArray[13] = v31;
+ mBackingArray[14] = v32;
+ mBackingArray[15] = v33;
+
+ return this;
+ }
+
+ /**
+ * Applies scaling factors to `this` Matrix44, then returns self. Pass 1s for no change.
+ *
+ * @param x Scaling factor for the x-axis
+ * @param y Scaling factor for the y-axis
+ * @param z Scaling factor for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 scale(float x, float y, float z) {
+ mBackingArray[0] *= x;
+ mBackingArray[4] *= x;
+ mBackingArray[8] *= x;
+ mBackingArray[12] *= x;
+ mBackingArray[1] *= y;
+ mBackingArray[5] *= y;
+ mBackingArray[9] *= y;
+ mBackingArray[13] *= y;
+ mBackingArray[2] *= z;
+ mBackingArray[6] *= z;
+ mBackingArray[10] *= z;
+ mBackingArray[14] *= z;
+
+ return this;
+ }
+
+ /**
+ * Applies a translation to `this` Matrix44, then returns self.
+ *
+ * @param x Translation for the x-axis
+ * @param y Translation for the y-axis
+ * @param z Translation for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 translate(float x, float y, float z) {
+ float newX = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + mBackingArray[3];
+ float newY = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + mBackingArray[7];
+ float newZ = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + mBackingArray[11];
+ float newW = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + mBackingArray[15];
+
+ mBackingArray[3] = newX;
+ mBackingArray[7] = newY;
+ mBackingArray[11] = newZ;
+ mBackingArray[15] = newW;
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("""
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ """, mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15]);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Matrix44) {
+ return Arrays.equals(mBackingArray, ((Matrix44) obj).mBackingArray);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) mBackingArray[0] + (int) mBackingArray[1] + (int) mBackingArray[2]
+ + (int) mBackingArray[3] + (int) mBackingArray[4] + (int) mBackingArray[5]
+ + (int) mBackingArray[6] + (int) mBackingArray[7] + (int) mBackingArray[8]
+ + (int) mBackingArray[9] + (int) mBackingArray[10] + (int) mBackingArray[11]
+ + (int) mBackingArray[12] + (int) mBackingArray[13] + (int) mBackingArray[14]
+ + (int) mBackingArray[15];
+ }
+
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 3ef714ed..a90961e 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -100,6 +100,71 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static @FontFamily.Builder.VariableFontFamilyType int resolveVarFamilyType(
+ @NonNull FontConfig.FontFamily xmlFamily,
+ @Nullable String familyName) {
+ int wghtCount = 0;
+ int italCount = 0;
+ int targetFonts = 0;
+ boolean hasItalicFont = false;
+
+ List<FontConfig.Font> fonts = xmlFamily.getFontList();
+ for (int i = 0; i < fonts.size(); ++i) {
+ FontConfig.Font font = fonts.get(i);
+
+ if (familyName == null) { // for default family
+ if (font.getFontFamilyName() != null) {
+ continue; // this font is not for the default family.
+ }
+ } else { // for the specific family
+ if (!familyName.equals(font.getFontFamilyName())) {
+ continue; // this font is not for given family.
+ }
+ }
+
+ final int varTypeAxes = font.getVarTypeAxes();
+ if (varTypeAxes == 0) {
+ // If we see static font, we can immediately return as VAR_TYPE_NONE.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_WGHT) != 0) {
+ wghtCount++;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_ITAL) != 0) {
+ italCount++;
+ }
+
+ if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) {
+ hasItalicFont = true;
+ }
+ targetFonts++;
+ }
+
+ if (italCount == 0) { // No ital font.
+ if (targetFonts == 1 && wghtCount == 1) {
+ // If there is only single font that has wght, use it for regular style and
+ // use synthetic bolding for italic.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+ } else if (targetFonts == 2 && wghtCount == 2 && hasItalicFont) {
+ // If there are two fonts and italic font is available, use them for regular and
+ // italic separately. (It is impossible to have two italic fonts. It will end up
+ // with Typeface creation failure.)
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
+ }
+ } else if (italCount == 1) {
+ // If ital font is included, a single font should support both wght and ital.
+ if (wghtCount == 1 && targetFonts == 1) {
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+ }
+ }
+ // Otherwise, unsupported.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
@NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
@@ -126,7 +191,7 @@
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
+ defaultFonts, languageTags, variant, resolveVarFamilyType(xmlFamily, null), false,
cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
@@ -145,7 +210,7 @@
}
} else {
final FontFamily family = createFontFamily(fallback, languageTags, variant,
- xmlFamily.getVariableFontFamilyType(), false, cache);
+ resolveVarFamilyType(xmlFamily, name), false, cache);
if (family != null) {
familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
@@ -217,7 +282,8 @@
final FontFamily family = createFontFamily(
xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
- xmlFamily.getVariableFontFamilyType(),
+ resolveVarFamilyType(xmlFamily,
+ null /* all fonts under named family should be treated as default */),
true, // named family is always default
bufferCache);
if (family == null) {
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 4cdc06a..a12fa5f 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,6 +39,7 @@
}
// Sources that have no dependencies that can be used directly downstream of this library
+// TODO(b/322791067): move these sources to WindowManager-Shell-shared
filegroup {
name: "wm_shell_util-sources",
srcs: [
@@ -137,6 +138,12 @@
},
}
+java_library {
+ name: "WindowManager-Shell-shared",
+
+ srcs: ["shared/**/*.java"],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
@@ -162,6 +169,7 @@
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
"perfetto_trace_java_protos",
"dagger2",
"jsr330",
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0e04658..2b95f30 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -30,13 +30,6 @@
}
flag {
- name: "enable_pip_ui_state_on_entering"
- namespace: "multitasking"
- description: "Enables PiP UI state callback on entering"
- bug: "303718131"
-}
-
-flag {
name: "enable_pip2_implementation"
namespace: "multitasking"
description: "Enables the new implementation of PiP (PiP2)"
@@ -57,3 +50,10 @@
description: "Enables new animations for expand and collapse for bubbles"
bug: "311450609"
}
+
+flag {
+ name: "enable_pip_umo_experience"
+ namespace: "multitasking"
+ description: "Enables new UMO experience for PiP menu"
+ bug: "307998712"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
index 4c76168..398fd55 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -22,7 +22,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
@@ -30,6 +29,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -37,10 +38,11 @@
private lateinit var bubbleTaskView: BubbleTaskView
private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var taskView: TaskView
@Before
fun setUp() {
- val taskView = TaskView(context, mock<TaskViewTaskController>())
+ taskView = mock()
bubbleTaskView = BubbleTaskView(taskView, directExecutor())
}
@@ -72,4 +74,19 @@
assertThat(actualTaskId).isEqualTo(123)
assertThat(actualComponentName).isEqualTo(componentName)
}
+
+ @Test
+ fun cleanup_invalidTaskId_doesNotRemoveTask() {
+ bubbleTaskView.cleanup()
+ verify(taskView, never()).removeTask()
+ }
+
+ @Test
+ fun cleanup_validTaskId_removesTask() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ bubbleTaskView.cleanup()
+ verify(taskView).removeTask()
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
index 7e95814..fd3a749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.graphics.Point;
import android.util.RotationUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 936faa3..dcd4062 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
@@ -28,14 +28,13 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -53,6 +52,8 @@
/** Various utility functions for transitions. */
public class TransitionUtil {
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8241e1a..8d30db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 44ee561..539832e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -46,7 +46,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index efa5a1a..0272f1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -38,7 +38,7 @@
import android.window.TransitionInfo;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4e852c..1f9358e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -38,9 +38,9 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 87c8f52..f32f030 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -51,6 +51,8 @@
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleInfo;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
import java.util.List;
@@ -105,6 +107,8 @@
private BubbleExpandedView mExpandedView;
@Nullable
private BubbleBarExpandedView mBubbleBarExpandedView;
+ @Nullable
+ private BubbleTaskView mBubbleTaskView;
private BubbleViewInfoTask mInflationTask;
private boolean mInflateSynchronously;
@@ -394,6 +398,21 @@
}
/**
+ * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new
+ * instance of {@link BubbleTaskView} is created.
+ */
+ public BubbleTaskView getOrCreateBubbleTaskView(Context context, BubbleController controller) {
+ if (mBubbleTaskView == null) {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
+ controller.getTaskOrganizer(),
+ controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
+ TaskView taskView = new TaskView(context, taskViewTaskController);
+ mBubbleTaskView = new BubbleTaskView(taskView, controller.getMainExecutor());
+ }
+ return mBubbleTaskView;
+ }
+
+ /**
* @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise.
*/
String getShortcutId() {
@@ -415,6 +434,10 @@
* the bubble.
*/
void cleanupExpandedView() {
+ cleanupExpandedView(true);
+ }
+
+ private void cleanupExpandedView(boolean cleanupTaskView) {
if (mExpandedView != null) {
mExpandedView.cleanUpExpandedState();
mExpandedView = null;
@@ -423,17 +446,37 @@
mBubbleBarExpandedView.cleanUpExpandedState();
mBubbleBarExpandedView = null;
}
+ if (cleanupTaskView) {
+ cleanupTaskView();
+ }
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
}
mIntentActive = false;
}
+ private void cleanupTaskView() {
+ if (mBubbleTaskView != null) {
+ mBubbleTaskView.cleanup();
+ mBubbleTaskView = null;
+ }
+ }
+
/**
* Call when all the views should be removed/cleaned up.
*/
void cleanupViews() {
- cleanupExpandedView();
+ cleanupViews(true);
+ }
+
+ /**
+ * Call when all the views should be removed/cleaned up.
+ *
+ * <p>If we're switching between bar and floating modes, pass {@code false} on
+ * {@code cleanupTaskView} to avoid recreating it in the new mode.
+ */
+ void cleanupViews(boolean cleanupTaskView) {
+ cleanupExpandedView(cleanupTaskView);
mIconView = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a5f7880..aea3ca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -173,7 +173,7 @@
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -197,12 +197,12 @@
private final Handler mMainHandler;
private final ShellExecutor mBackgroundExecutor;
- private BubbleLogger mLogger;
- private BubbleData mBubbleData;
+ private final BubbleLogger mLogger;
+ private final BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubblePositioner mBubblePositioner;
+ private final BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
// Tracks the id of the current (foreground) user.
@@ -232,13 +232,17 @@
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+ /**
+ * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+ */
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
+ /**
+ * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+ */
+ private final Rect mScreenBounds = new Rect();
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
@@ -253,9 +257,9 @@
private boolean mIsStatusBarShade = true;
/** One handed mode controller to register transition listener. */
- private Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private Optional<DragAndDropController> mDragAndDropController;
+ private final Optional<DragAndDropController> mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -731,9 +735,11 @@
}
} else {
if (mStackView == null) {
+ BubbleStackViewManager bubbleStackViewManager =
+ BubbleStackViewManager.fromBubbleController(this);
mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator, this, mMainExecutor);
+ mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+ mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -893,7 +899,6 @@
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- @VisibleForTesting
public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
@@ -1047,7 +1052,6 @@
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
- @VisibleForTesting
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
@@ -1105,9 +1109,8 @@
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
- int bubbleBarOffsetY) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+ public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1273,9 +1276,17 @@
mBubbleData.setExpanded(true);
}
} else {
- // App bubble does not exist, lets add and expand it
- Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
- Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ // Check if it exists in the overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ if (b != null) {
+ // It's in the overflow, so remove it & reinflate
+ Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow");
+ mBubbleData.removeOverflowBubble(b);
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
+ b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ }
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1366,8 +1377,9 @@
mStackView.resetOverflowView();
mStackView.removeAllViews();
}
- // cleanup existing bubble views so they can be recreated later if needed.
- mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+ // cleanup existing bubble views so they can be recreated later if needed, but retain
+ // TaskView.
+ mBubbleData.getBubbles().forEach(b -> b.cleanupViews(/* cleanupTaskView= */ false));
// remove the current bubble container from window manager, null it out, and create a new
// container based on the current mode.
@@ -1478,7 +1490,6 @@
* <p>
* Must be called from the main thread.
*/
- @VisibleForTesting
@MainThread
public void removeBubble(String key, int reason) {
if (mBubbleData.hasAnyBubbleWithKey(key)) {
@@ -1486,36 +1497,6 @@
}
}
- // TODO(b/316358859): remove this method after task views are shared across modes
- /**
- * Removes the bubble with the given key after task removal, unless the task was removed as
- * a result of mode switching, in which case, the bubble isn't removed because it will be
- * re-inflated for the new mode.
- */
- @MainThread
- public void removeFloatingBubbleAfterTaskRemoval(String key, int reason) {
- // if we're floating remove the bubble. otherwise, we're here because the task was removed
- // after switching modes. See b/316358859
- if (!isShowingAsBubbleBar()) {
- removeBubble(key, reason);
- }
- }
-
- // TODO(b/316358859): remove this method after task views are shared across modes
- /**
- * Removes the bubble with the given key after task removal, unless the task was removed as
- * a result of mode switching, in which case, the bubble isn't removed because it will be
- * re-inflated for the new mode.
- */
- @MainThread
- public void removeBarBubbleAfterTaskRemoval(String key, int reason) {
- // if we're showing as bubble bar remove the bubble. otherwise, we're here because the task
- // was removed after switching modes. See b/316358859
- if (isShowingAsBubbleBar()) {
- removeBubble(key, reason);
- }
- }
-
/**
* Removes all the bubbles.
* <p>
@@ -2198,10 +2179,10 @@
}
@Override
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarOffsetX, bubbleBarOffsetY));
+ key, bubbleBarBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6e0c804..127c7e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -491,6 +491,19 @@
}
/**
+ * Explicitly removes a bubble from the overflow, if it exists.
+ *
+ * @param bubble the bubble to remove.
+ */
+ public void removeOverflowBubble(Bubble bubble) {
+ if (bubble == null) return;
+ if (mOverflowBubbles.remove(bubble)) {
+ mStateChange.removedOverflowBubble = bubble;
+ dispatchPendingChanges();
+ }
+ }
+
+ /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -1145,7 +1158,6 @@
return null;
}
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 9f7d0ac..efc4d8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -27,12 +27,10 @@
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -49,7 +47,6 @@
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
-import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
@@ -311,8 +308,7 @@
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
- mController.removeFloatingBubbleAfterTaskRemoval(
- mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
if (mTaskView != null) {
// Release the surface
@@ -1105,32 +1101,11 @@
return ((LinearLayout.LayoutParams) mManageButton.getLayoutParams()).getMarginStart();
}
- /**
- * Cleans up anything related to the task. The TaskView itself is released after the task
- * has been removed.
- *
- * If this view should be reused after this method is called, then
- * {@link #initialize(BubbleController, BubbleStackView, boolean, BubbleTaskView)}
- * must be invoked first.
- */
+ /** Hide the task view. */
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
}
- if (getTaskId() != INVALID_TASK_ID) {
- // Ensure the task is removed from WM
- if (ENABLE_SHELL_TRANSITIONS) {
- if (mTaskView != null) {
- mTaskView.removeTask();
- }
- } else {
- try {
- ActivityTaskManager.getService().removeTask(getTaskId());
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- }
if (mTaskView != null) {
mTaskView.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a76bd26..d62c86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -97,7 +96,7 @@
private PointF mRestingStackPosition;
private boolean mShowingInBubbleBar;
- private final Point mBubbleBarPosition = new Point();
+ private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -791,15 +790,10 @@
}
/**
- * Sets the position of the bubble bar in screen coordinates.
- *
- * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
- * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ * Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(int offsetX, int offsetY) {
- mBubbleBarPosition.set(
- getAvailableRect().width() - offsetX,
- getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
+ public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ mBubbleBarBounds.set(bubbleBarBounds);
}
/**
@@ -820,7 +814,7 @@
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarPosition.y - mExpandedViewPadding;
+ return mBubbleBarBounds.top - mExpandedViewPadding;
}
/**
@@ -831,9 +825,9 @@
}
/**
- * Returns the on screen co-ordinates of the bubble bar.
+ * Returns the display coordinates of the bubble bar.
*/
- public Point getBubbleBarPosition() {
- return mBubbleBarPosition;
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a619401..9facef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -204,7 +204,7 @@
Choreographer.getInstance().postFrameCallback(frameCallback);
}
};
- private final BubbleController mBubbleController;
+ private final BubbleStackViewManager mManager;
private final BubbleData mBubbleData;
private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
@@ -858,6 +858,7 @@
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
+ private StackEducationView.Manager mStackEducationViewManager;
private ManageEducationView mManageEduView;
private DismissView mDismissView;
@@ -873,15 +874,16 @@
private BubblePositioner mPositioner;
@SuppressLint("ClickableViewAccessibility")
- public BubbleStackView(Context context, BubbleController bubbleController,
- BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+ public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+ BubblePositioner bubblePositioner, BubbleData data,
+ @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
- mBubbleController = bubbleController;
+ mManager = bubbleStackViewManager;
mBubbleData = data;
mSysuiProxyProvider = sysuiProxyProvider;
@@ -893,7 +895,7 @@
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mPositioner = mBubbleController.getPositioner();
+ mPositioner = bubblePositioner;
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.dialogCornerRadius});
@@ -903,7 +905,7 @@
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
mExpandedViewTemporarilyHidden = false;
- mBubbleController.onAllBubblesAnimatedOut();
+ mManager.onAllBubblesAnimatedOut();
}
};
mStackAnimationController = new StackAnimationController(
@@ -1383,7 +1385,9 @@
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
}
return showStackEdu();
@@ -1412,7 +1416,9 @@
private void updateUserEdu() {
if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
showStackEdu();
}
@@ -2106,7 +2112,7 @@
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
if (!notifPanelExpanded && mIsExpanded) {
startMonitoringSwipeUpGesture();
}
@@ -2227,7 +2233,7 @@
*/
void hideCurrentInputMethod() {
mPositioner.setImeVisible(false, 0);
- mBubbleController.hideCurrentInputMethod();
+ mManager.hideCurrentInputMethod();
}
/** Set the stack position to whatever the positioner says. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 0000000..fb597a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+ /** Notifies that all bubbles animated out. */
+ fun onAllBubblesAnimatedOut()
+
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+ /**
+ * Checks the current expansion state of the notification panel, and invokes [callback] with the
+ * result.
+ */
+ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+ /** Requests to hide the current input method. */
+ fun hideCurrentInputMethod()
+
+ companion object {
+
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+ override fun onAllBubblesAnimatedOut() {
+ controller.onAllBubblesAnimatedOut()
+ }
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+ controller.updateWindowFlagsForBackpress(interceptBack)
+ }
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+ controller.isNotificationPanelExpanded(callback)
+ }
+
+ override fun hideCurrentInputMethod() {
+ controller.hideCurrentInputMethod()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
index 2fcd133..65f8e48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -16,10 +16,14 @@
package com.android.wm.shell.bubbles
+import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.content.ComponentName
+import android.os.RemoteException
+import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import java.util.concurrent.Executor
/**
@@ -78,4 +82,28 @@
init {
taskView.setListener(executor, listener)
}
+
+ /**
+ * Removes the [TaskView] from window manager.
+ *
+ * This should be called after all other cleanup animations have finished.
+ */
+ fun cleanup() {
+ if (taskId != INVALID_TASK_ID) {
+ // Ensure the task is removed from WM
+ if (ENABLE_SHELL_TRANSITIONS) {
+ taskView.removeTask()
+ } else {
+ try {
+ ActivityTaskManager.getService().removeTask(taskId)
+ } catch (e: RemoteException) {
+ Log.w(TAG, e.message ?: "")
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "BubbleTaskView"
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5855a81..5fc67d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -29,7 +29,6 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Log;
import android.view.View;
@@ -183,8 +182,11 @@
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
- mController.removeBarBubbleAfterTaskRemoval(
- mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ mTaskView = null;
}
}
@@ -228,24 +230,6 @@
return false;
}
- /** Cleans up anything related to the task and {@code TaskView}. */
- public void cleanUpTaskView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (mTaskId != INVALID_TASK_ID) {
- try {
- ActivityTaskManager.getService().removeTask(mTaskId);
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- if (mTaskView != null) {
- mTaskView.release();
- mTaskView = null;
- }
- }
-
/** Returns the bubble key associated with this view. */
@Nullable
public String getBubbleKey() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index c3d899e..5fc10a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -46,8 +46,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
-import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -175,7 +173,7 @@
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
- BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
@@ -205,7 +203,7 @@
R.layout.bubble_view, stackView, false /* attachToRoot */);
info.imageView.initialize(controller.getPositioner());
- BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
info.expandedView.initialize(
@@ -225,15 +223,6 @@
}
return info;
}
-
- private static BubbleTaskView createBubbleTaskView(
- Context context, BubbleController controller) {
- TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
- controller.getTaskOrganizer(),
- controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
- TaskView taskView = new TaskView(context, taskViewTaskController);
- return new BubbleTaskView(taskView, controller.getMainExecutor());
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
index 9e8a385..c1f704a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -24,8 +24,8 @@
import androidx.annotation.NonNull;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
/**
* Observer used to identify tasks that are opening or moving to front. If a bubble activity is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5776ad1..7a5afec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
+import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
/**
@@ -29,7 +30,7 @@
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
+ oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
oneway void removeBubble(in String key) = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 95f1017..c4108c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -35,7 +35,7 @@
class StackEducationView(
context: Context,
private val positioner: BubblePositioner,
- private val controller: BubbleController
+ private val manager: Manager
) : LinearLayout(context) {
companion object {
@@ -44,6 +44,12 @@
private const val ANIMATE_DURATION_SHORT: Long = 40
}
+ /** Callbacks to notify managers of [StackEducationView] about events. */
+ interface Manager {
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+ }
+
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
@@ -93,7 +99,7 @@
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setOnKeyListener(null)
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
}
private fun setTextColor() {
@@ -124,7 +130,7 @@
isHiding = false
if (visibility == VISIBLE) return false
- controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(true /* interceptBack */)
layoutParams.width =
if (positioner.isLargeScreen || positioner.isLandscape)
context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -185,7 +191,7 @@
if (visibility != VISIBLE || isHiding) return
isHiding = true
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
.setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 893a87f..84a616f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Log;
import android.util.Size;
import android.view.View;
@@ -149,12 +150,12 @@
bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarPosition.x,
- bubbleBarPosition.y);
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 3cf23ac..00d683e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -272,7 +272,6 @@
if (mTaskView != null) {
removeView(mTaskView);
}
- mBubbleTaskViewHelper.cleanUpTaskView();
}
mMenuViewController.hideMenu(false /* animated */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 49db8d9..e8c809e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -20,10 +20,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
+import com.android.wm.shell.shared.TransitionUtil;
+
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
@@ -126,7 +127,7 @@
WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c0fc02fa..f82212d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -86,10 +86,10 @@
) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
- val visibleTasks = getVisibleTaskCount(displayId)
+ val visibleTasksCount = getVisibleTaskCount(displayId)
val stashed = isStashed(displayId)
executor.execute {
- visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
@@ -222,10 +222,8 @@
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- // Task removed from other display, check if we should notify listeners
- if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
- notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
- }
+ notifyVisibleTaskListeners(otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size)
}
}
}
@@ -248,15 +246,15 @@
)
}
- // Check if count changed and if there was no tasks or this is the first task
- if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
- notifyVisibleTaskListeners(displayId, newCount > 0)
+ // Check if count changed
+ if (prevCount != newCount) {
+ notifyVisibleTaskListeners(displayId, newCount)
}
}
- private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -379,9 +377,9 @@
*/
interface VisibleTasksListener {
/**
- * Called when the desktop starts or stops showing freeform tasks.
+ * Called when the desktop changes the number of visible freeform tasks.
*/
- fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
/**
* Called when the desktop stashed status changes.
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 28c06a4..a089e81 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
@@ -106,8 +106,8 @@
visualIndicator = null
}
private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
- launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
}
}
private val dragToDesktopStateListener = object : DragToDesktopStateListener {
@@ -1033,14 +1033,16 @@
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visible
+ visibleTasksCount
)
- remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ remoteListener.call {
+ l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 731fec7..39610e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -26,6 +26,7 @@
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -33,7 +34,6 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.util.TransitionUtil
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 39128a8..8ed87f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -22,8 +22,8 @@
*/
interface IDesktopTaskListener {
- /** Desktop task visibility has change. Visible if at least 1 task is visible. */
- oneway void onVisibilityChanged(int displayId, boolean visible);
+ /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
+ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
/** Desktop task stashed status has changed. */
oneway void onStashedChanged(int displayId, boolean stashed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index e63bbc0..73de231 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -27,7 +27,7 @@
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e739266..896ca96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -71,11 +71,11 @@
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d16a692..c2f4d72a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -71,9 +71,9 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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 1232baa..97d3457 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
@@ -58,10 +58,10 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
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;
import java.util.ArrayList;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 5de8a9b..e8894a83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -48,9 +48,9 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index af05aa2..e5045ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -44,6 +44,8 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -64,8 +66,6 @@
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -135,13 +135,13 @@
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
-import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 84f21f6..198ec82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,8 +37,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Objects;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 628ce27..b03daaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -27,8 +27,8 @@
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8c2203e..8746b8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -24,7 +24,7 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,11 +48,11 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 193a4fb..c70a821 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,8 +109,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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 af31f5f..cb2944c 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
@@ -32,7 +32,7 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 293b660..4c4c580 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,7 +39,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b012d35..1be85d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -56,7 +56,7 @@
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
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 67fc7e2..5e79681 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
@@ -37,9 +37,9 @@
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -80,13 +80,13 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
import com.android.wm.shell.transition.tracing.TransitionTracer;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 98d343b..c26604a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -35,6 +35,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -43,7 +44,6 @@
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index dab762f..fa0aba5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1190,6 +1190,23 @@
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
+ @Test
+ public void test_removeOverflowBubble() {
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
+
+ mBubbleData.removeOverflowBubble(mBubbleA1);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
+ assertOverflowChangedTo(ImmutableList.of());
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4878df8..75965d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -56,9 +56,7 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/**
- * Tests for loading / inflating views & icons for a bubble.
- */
+/** Tests for loading / inflating views & icons for a bubble. */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -76,25 +74,33 @@
@Before
fun setup() {
metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
- iconFactory = BubbleIconFactory(context,
+ iconFactory =
+ BubbleIconFactory(
+ context,
60,
30,
Color.RED,
- mContext.resources.getDimensionPixelSize(
- R.dimen.importance_ring_stroke_width))
+ mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+ )
mainExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler,
- mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
val bubblePositioner = BubblePositioner(context, windowManager)
- val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
- BubbleEducationController(context), mainExecutor)
+ val bubbleData =
+ BubbleData(
+ context,
+ mock<BubbleLogger>(),
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor
+ )
val surfaceSynchronizer = { obj: Runnable -> obj.run() }
- bubbleController = BubbleController(
+ bubbleController =
+ BubbleController(
context,
shellInit,
shellCommandHandler,
@@ -122,18 +128,36 @@
mock<Transitions>(),
mock<SyncTransactionQueue>(),
mock<IWindowManager>(),
- mock<BubbleProperties>())
+ mock<BubbleProperties>()
+ )
- bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
+ val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ bubblePositioner,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleController,
+ mainExecutor
+ )
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
}
@Test
fun testPopulate() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
- bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populate(
+ context,
+ bubbleController,
+ bubbleStackView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNotNull()
@@ -151,9 +175,15 @@
@Test
fun testPopulateForBubbleBar() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNull()
@@ -176,12 +206,18 @@
// exception here if the app has an issue loading the shortcut icon; we default to
// the app icon in that case / none of the icons will be null.
val mockIconFactory = mock<BubbleIconFactory>()
- whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
- any())).doThrow(RuntimeException())
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any()))
+ .doThrow(RuntimeException())
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- true /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ true /* skipInflation */
+ )
assertThat(info).isNotNull()
assertThat(info?.shortcutInfo).isNotNull()
@@ -194,8 +230,17 @@
private fun createBubbleWithShortcut(): Bubble {
val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
- return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
- "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
- mainExecutor, metadataFlagListener)
+ return Bubble(
+ "mockKey",
+ shortcutInfo,
+ 1000,
+ Resources.ID_NULL,
+ "mockTitle",
+ 0 /* taskId */,
+ "mockLocus",
+ true /* isDismissible */,
+ mainExecutor,
+ metadataFlagListener
+ )
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3fe78ef..445f74a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -124,7 +124,7 @@
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@@ -148,7 +148,7 @@
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -162,8 +162,8 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
@@ -175,16 +175,16 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
executor.flushAll()
// Listener for secondary display is notified
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
// No changes to listener for default display
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
@@ -198,7 +198,7 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
// Mark task 1 visible on secondary display
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
@@ -208,11 +208,11 @@
// 1 - visible task added
// 2 - visible task removed
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// Secondary display should have 1 call for visible task added
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@Test
@@ -224,17 +224,17 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
}
@Test
@@ -397,8 +397,8 @@
}
class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
- var hasVisibleTasksOnDefaultDisplay = false
- var hasVisibleTasksOnSecondaryDisplay = false
+ var visibleTasksCountOnDefaultDisplay = 0
+ var visibleTasksCountOnSecondaryDisplay = 0
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
@@ -409,14 +409,14 @@
var stashedChangesOnDefaultDisplay = 0
var stashedChangesOnSecondaryDisplay = 0
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
- hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnDefaultDisplay = visibleTasksCount
visibleChangesOnDefaultDisplay++
}
SECOND_DISPLAY -> {
- hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnSecondaryDisplay = visibleTasksCount
visibleChangesOnSecondaryDisplay++
}
else -> fail("Visible task listener received unexpected display id: $displayId")
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 008ea3a..14b8d8d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -341,6 +341,10 @@
mCanvas->concat(matrix);
}
+void SkiaCanvas::concat(const SkM44& matrix) {
+ mCanvas->concat(matrix);
+}
+
void SkiaCanvas::rotate(float degrees) {
mCanvas->rotate(degrees);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4bf1790..5e3553b 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -86,6 +86,7 @@
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
virtual void concat(const SkMatrix& matrix) override;
+ virtual void concat(const SkM44& matrix) override;
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
virtual void skew(float sx, float sy) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9ec023b..20e3ad2 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -175,6 +175,7 @@
virtual void setMatrix(const SkMatrix& matrix) = 0;
virtual void concat(const SkMatrix& matrix) = 0;
+ virtual void concat(const SkM44& matrix) = 0;
virtual void rotate(float degrees) = 0;
virtual void scale(float sx, float sy) = 0;
virtual void skew(float sx, float sy) = 0;
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index d572593..295f4dc 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -158,6 +158,13 @@
get_canvas(canvasHandle)->concat(*matrix);
}
+static void concat44(JNIEnv* env, jobject obj, jlong canvasHandle, jfloatArray arr) {
+ jfloat* matVals = env->GetFloatArrayElements(arr, 0);
+ const SkM44 matrix = SkM44::RowMajor(matVals);
+ get_canvas(canvasHandle)->concat(matrix);
+ env->ReleaseFloatArrayElements(arr, matVals, 0);
+}
+
static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
@@ -781,6 +788,7 @@
{"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
{"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
{"nConcat","(JJ)V", (void*) CanvasJNI::concat},
+ {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
{"nRotate","(JF)V", (void*) CanvasJNI::rotate},
{"nScale","(JFF)V", (void*) CanvasJNI::scale},
{"nSkew","(JFF)V", (void*) CanvasJNI::skew},
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d..b5f7caa 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <sstream>
+
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+ VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
+ if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+ new VkPhysicalDeviceFaultFeaturesEXT;
+ deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ deviceFaultFeatures->pNext = nullptr;
+ *tailPNext = deviceFaultFeatures;
+ tailPNext = &deviceFaultFeatures->pNext;
+ }
+
// query to get the physical device features
mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
// this looks like it would slow things down,
@@ -405,6 +418,79 @@
});
}
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":"
+ << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":"
+ << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+} // anonymous namespace
+
static void onGrContextReleased(void* context) {
VulkanManager* manager = (VulkanManager*)context;
manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
+ backendContext.fDeviceLostContext = nullptr;
+ backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+ ? deviceLostProcRenderThread
+ : deviceLostProcUploadThread;
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4918289..69708ec 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -23,6 +23,7 @@
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
@@ -80,6 +81,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
@@ -100,6 +102,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -7838,6 +7841,51 @@
}
/**
+ * Returns a Set of unique Integers corresponding to audio device type identifiers that can
+ * <i>potentially</i> be connected to the system and meeting the criteria specified in the
+ * <code>direction</code> parameter.
+ * Note that this set contains {@link AudioDeviceInfo} device type identifiers for both devices
+ * currently available <i>and</i> those that can be available if the user connects an audio
+ * peripheral. Examples include TYPE_WIRED_HEADSET if the Android device supports an analog
+ * headset jack or TYPE_USB_DEVICE if the Android device supports a USB host-mode port.
+ * These are generally a superset of device type identifiers associated with the
+ * AudioDeviceInfo objects returned from AudioManager.getDevices().
+ * @param direction The constant specifying whether input or output devices are queried.
+ * @see #GET_DEVICES_OUTPUTS
+ * @see #GET_DEVICES_INPUTS
+ * @return A (possibly zero-length) Set of Integer objects corresponding to the audio
+ * device types of devices supported by the implementation.
+ * @throws IllegalArgumentException If an invalid direction constant is specified.
+ */
+ @FlaggedApi(FLAG_SUPPORTED_DEVICE_TYPES_API)
+ public @NonNull Set<Integer>
+ getSupportedDeviceTypes(int direction) {
+ if (direction != GET_DEVICES_OUTPUTS && direction != GET_DEVICES_INPUTS) {
+ throw new IllegalArgumentException("AudioManager.getSupportedDeviceTypes("
+ + Integer.toHexString(direction) + ") - Invalid.");
+ }
+
+ IntArray internalDeviceTypes = new IntArray();
+ int status = AudioSystem.getSupportedDeviceTypes(direction, internalDeviceTypes);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "AudioManager.getSupportedDeviceTypes(" + direction + ") failed. status:"
+ + status);
+ }
+
+ // convert to external (AudioDeviceInfo.getType()) device IDs
+ HashSet<Integer> externalDeviceTypes = new HashSet<Integer>();
+ for (int index = 0; index < internalDeviceTypes.size(); index++) {
+ // Set will eliminate any duplicates which AudioSystem.getSupportedDeviceTypes()
+ // returns
+ externalDeviceTypes.add(
+ AudioDeviceInfo.convertInternalDeviceToDeviceType(
+ internalDeviceTypes.get(index)));
+ }
+
+ return externalDeviceTypes;
+ }
+
+ /**
* Returns an array of {@link AudioDeviceInfo} objects corresponding to the audio devices
* currently connected to the system and meeting the criteria specified in the
* <code>flags</code> parameter.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0f6cbff..f73be35f 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -36,6 +36,7 @@
import android.os.Parcel;
import android.os.Vibrator;
import android.telephony.TelephonyManager;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -1947,6 +1948,8 @@
/** @hide */
public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
/** @hide */
+ public static native int getSupportedDeviceTypes(int flags, IntArray internalDeviceTypes);
+ /** @hide */
public static native int createAudioPatch(AudioPatch[] patch,
AudioPortConfig[] sources, AudioPortConfig[] sinks);
/** @hide */
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index 72e6db8..5ed8d40 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -86,7 +86,10 @@
*/
public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
- /** */
+ /**
+ * Caused by failing to load data via cleartext HTTP, when the app's network security
+ * configuration does not permit it.
+ */
public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
/** Caused by reading data out of the data bounds. */
@@ -146,6 +149,9 @@
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
public @interface ErrorCode {}
+ /** Special value for unknown {@linkplain #getTimeSinceCreatedMillis() time since creation}. */
+ public static final int TIME_SINCE_CREATED_UNKNOWN = -1;
+
private final @ErrorCode int mErrorCode;
@SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
private final long mTimeSinceCreatedMillis;
@@ -174,16 +180,16 @@
}
/**
- * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
- * unknown.
+ * Gets the elapsed time since creating of the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
*
- * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
- * unknown.
+ * @return The elapsed time since creating the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see LogSessionId
* @see EditingSession
*/
@Override
- @IntRange(from = -1)
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN)
public long getTimeSinceCreatedMillis() {
return mTimeSinceCreatedMillis;
}
@@ -283,7 +289,7 @@
public Builder(@FinalState int finalState) {
mFinalState = finalState;
mErrorCode = ERROR_CODE_NONE;
- mTimeSinceCreatedMillis = -1;
+ mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
mMetricsBundle = new Bundle();
}
@@ -291,11 +297,11 @@
* Sets the elapsed time since creating the editing session, in milliseconds.
*
* @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
- * milliseconds, or -1 if the value is unknown.
+ * milliseconds, or {@link #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see #getTimeSinceCreatedMillis()
*/
public @NonNull Builder setTimeSinceCreatedMillis(
- @IntRange(from = -1) long timeSinceCreatedMillis) {
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN) long timeSinceCreatedMillis) {
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
return this;
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 8978277..b644621 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -69,4 +69,6 @@
// For ad response
void onAdResponse(in AdResponse response, int seq);
void onAdBufferConsumed(in AdBuffer buffer, int seq);
+
+ void onTvInputSessionData(in String type, in Bundle data, int seq);
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 2f6575e..84c197d 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -152,4 +152,6 @@
// For freezing video playback
void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
+
+ void notifyTvAdSessionData(in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index a93f18d..7b20c39 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -86,4 +86,6 @@
// For freezing video
void setVideoFrozen(boolean isFrozen);
+
+ void notifyTvAdSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 8e2702a..76e079f 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -68,4 +68,6 @@
// For messages sent from the TV input
void onTvMessage(int type, in Bundle data);
+
+ void onTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 921104d..999e2cf 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -82,6 +82,7 @@
private static final int DO_STOP_PLAYBACK = 33;
private static final int DO_START_PLAYBACK = 34;
private static final int DO_SET_VIDEO_FROZEN = 35;
+ private static final int DO_NOTIFY_AD_SESSION_DATA = 36;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -287,6 +288,7 @@
case DO_NOTIFY_TV_MESSAGE: {
SomeArgs args = (SomeArgs) msg.obj;
mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
+ args.recycle();
break;
}
case DO_STOP_PLAYBACK: {
@@ -301,6 +303,12 @@
mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
break;
}
+ case DO_NOTIFY_AD_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.notifyTvAdSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -488,6 +496,12 @@
}
@Override
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_AD_SESSION_DATA, type, data));
+ }
+
+ @Override
public void setVideoFrozen(boolean isFrozen) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
}
diff --git a/media/java/android/media/tv/SignalingDataInfo.aidl b/media/java/android/media/tv/SignalingDataInfo.aidl
new file mode 100644
index 0000000..7108f36
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataInfo;
diff --git a/media/java/android/media/tv/SignalingDataInfo.java b/media/java/android/media/tv/SignalingDataInfo.java
new file mode 100644
index 0000000..b29ea5c
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+/** @hide */
+public class SignalingDataInfo implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataInfo> CREATOR =
+ new Parcelable.Creator<SignalingDataInfo>() {
+ @Override
+ public SignalingDataInfo[] newArray(int size) {
+ return new SignalingDataInfo[size];
+ }
+
+ @Override
+ public SignalingDataInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataInfo(in);
+ }
+ };
+
+ private int mTableId;
+ private @NonNull String mTable;
+ private int mMetadataType;
+ private int mVersion;
+ private int mGroup;
+ private @NonNull String mEncoding;
+
+ public SignalingDataInfo(
+ int tableId,
+ @NonNull String table,
+ int metadataType,
+ int version,
+ int group,
+ @NonNull String encoding) {
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public @NonNull String getTable() {
+ return mTable;
+ }
+
+ public int getMetadataType() {
+ return mMetadataType;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public int getGroup() {
+ return mGroup;
+ }
+
+ public @NonNull String getEncoding() {
+ return mEncoding;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mTableId);
+ dest.writeString(mTable);
+ dest.writeInt(mMetadataType);
+ dest.writeInt(mVersion);
+ dest.writeInt(mGroup);
+ dest.writeString(mEncoding);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataInfo(@NonNull android.os.Parcel in) {
+ int tableId = in.readInt();
+ String table = in.readString();
+ int metadataType = in.readInt();
+ int version = in.readInt();
+ int group = in.readInt();
+ String encoding = in.readString();
+
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+}
diff --git a/media/java/android/media/tv/SignalingDataResponse.aidl b/media/java/android/media/tv/SignalingDataResponse.aidl
new file mode 100644
index 0000000..a548e4e
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataResponse;
diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java
new file mode 100644
index 0000000..3e4c790
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/** @hide */
+public class SignalingDataResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataResponse> CREATOR =
+ new Parcelable.Creator<SignalingDataResponse>() {
+ @Override
+ public SignalingDataResponse[] newArray(int size) {
+ return new SignalingDataResponse[size];
+ }
+
+ @Override
+ public SignalingDataResponse createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataResponse(in);
+ }
+ };
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
+ TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA;
+ private final @NonNull int[] mTableIds;
+ private final int mMetadataTypes;
+ private final @NonNull List<SignalingDataInfo> mSignalingDataInfoList;
+
+ public SignalingDataResponse(
+ int requestId,
+ int sequence,
+ @ResponseResult int responseResult,
+ @NonNull int[] tableIds,
+ int metadataTypes,
+ @NonNull List<SignalingDataInfo> signalingDataInfoList) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+
+ public @NonNull int[] getTableIds() {
+ return mTableIds;
+ }
+
+ public int getMetadataTypes() {
+ return mMetadataTypes;
+ }
+
+ public @NonNull List<SignalingDataInfo> getSignalingDataInfoList() {
+ return mSignalingDataInfoList;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeIntArray(mTableIds);
+ dest.writeInt(mMetadataTypes);
+ dest.writeParcelableList(mSignalingDataInfoList, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataResponse(@NonNull android.os.Parcel in) {
+ super(RESPONSE_TYPE, in);
+
+ int[] tableIds = in.createIntArray();
+ int metadataTypes = in.readInt();
+ List<SignalingDataInfo> signalingDataInfoList = new java.util.ArrayList<>();
+ in.readParcelableList(signalingDataInfoList, SignalingDataInfo.class.getClassLoader());
+
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index be1b675..8720bfe 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -535,6 +536,220 @@
*/
public static final int SIGNAL_STRENGTH_STRONG = 3;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_TUNED,
+ SESSION_DATA_TYPE_TRACK_SELECTED,
+ SESSION_DATA_TYPE_TRACKS_CHANGED,
+ SESSION_DATA_TYPE_VIDEO_AVAILABLE,
+ SESSION_DATA_TYPE_VIDEO_UNAVAILABLE,
+ SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_TYPE_AD_RESPONSE,
+ SESSION_DATA_TYPE_AD_BUFFER_CONSUMED,
+ SESSION_DATA_TYPE_TV_MESSAGE})
+ public @interface SessionDataType {}
+
+ /**
+ * Informs the application that the session has been tuned to the given channel.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_CHANNEL_URI
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+
+ /**
+ * Sends the type and ID of a selected track. This is used to inform the application that a
+ * specific track is selected.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACK_TYPE
+ * @see SESSION_DATA_KEY_TRACK_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+
+ /**
+ * Sends the list of all audio/video/subtitle tracks.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACKS
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+
+ /**
+ * Informs the application that the video is now available for watching.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+
+ /**
+ * Informs the application that the video became unavailable for some reason.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
+
+ /**
+ * Notifies response for broadcast info.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
+ "broadcast_info_response";
+
+ /**
+ * Notifies response for advertisement.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+
+ /**
+ * Notifies the advertisement buffer is consumed.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+
+ /**
+ * Sends the TV message.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#notifyTvMessage(int, Bundle)
+ * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_CHANNEL_URI,
+ SESSION_DATA_KEY_TRACK_TYPE,
+ SESSION_DATA_KEY_TRACK_ID,
+ SESSION_DATA_KEY_TRACKS,
+ SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON,
+ SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_KEY_AD_RESPONSE,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_TV_MESSAGE_TYPE})
+ public @interface SessionDataKey {}
+
+ /**
+ * The URI of a channel.
+ *
+ * <p> Type: android.net.Uri
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * The type of the track.
+ *
+ * <p>One of {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO},
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvTrackInfo#getType()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+
+ /**
+ * The ID of the track.
+ *
+ * <p> Type: String
+ *
+ * @see TvTrackInfo#getId()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+
+ /**
+ * A list which includes track information.
+ *
+ * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+
+ /**
+ * The reason why the video became unavailable.
+ * <p>The value can be {@link VIDEO_UNAVAILABLE_REASON_BUFFERING},
+ * {@link VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}, etc.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
+ "video_unavailable_reason";
+
+ /**
+ * An object of {@link BroadcastInfoResponse}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+
+ /**
+ * An object of {@link AdResponse}.
+ *
+ * <p> Type: android.media.tv.AdResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * The type of TV message.
+ * <p>It can be one of {@link TV_MESSAGE_TYPE_WATERMARK},
+ * {@link TV_MESSAGE_TYPE_CLOSED_CAPTION}, {@link TV_MESSAGE_TYPE_OTHER}
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+
+
/**
* An unknown state of the client pid gets from the TvInputManager. Client gets this value when
* query through {@link getClientPid(String sessionId)} fails.
@@ -1271,6 +1486,17 @@
});
}
}
+
+ void postTvInputSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getAdSession() != null) {
+ mSession.getAdSession().notifyTvInputSessionData(type, data);
+ }
+ }
+ });
+ }
}
/**
@@ -1820,6 +2046,18 @@
record.postAdBufferConsumed(buffer);
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvInputSessionData(type, data);
+ }
+ }
};
ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
@Override
@@ -3844,6 +4082,21 @@
}
}
+ /**
+ * Notifies data from session of linked TvAdService.
+ */
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvAdSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6301a27..a022b1c 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -34,6 +34,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
import android.os.AsyncTask;
@@ -1259,6 +1260,37 @@
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.ad.TvAdService} object via TvAdView.
+ *
+ * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
+ * related data to linked {@link android.media.tv.interactive.TvInteractiveAppService}, but
+ * don't work for {@link android.media.tv.ad.TvAdService}. The method is used specifically
+ * for {@link android.media.tv.ad.TvAdService} use cases.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @hide
+ */
+ public void notifyTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvInputSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1401,6 +1433,20 @@
public void onAdBufferReady(@NonNull AdBuffer buffer) {
}
+
+ /**
+ * Called when data from the linked {@link android.media.tv.ad.TvAdService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
/**
* Tunes to a given channel.
*
@@ -2166,6 +2212,10 @@
onAdBufferReady(buffer);
}
+ void notifyTvAdSessionData(String type, Bundle data) {
+ onTvAdSessionData(type, data);
+ }
+
void onTvMessageReceived(int type, Bundle data) {
onTvMessage(type, data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
index 34d96b3..49046d0 100644
--- a/media/java/android/media/tv/ad/ITvAdClient.aidl
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.os.Bundle;
import android.view.InputChannel;
/**
@@ -27,4 +28,11 @@
void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
void onSessionReleased(int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+ void onRequestCurrentVideoBounds(int seq);
+ void onRequestCurrentChannelUri(int seq);
+ void onRequestTrackInfoList(int seq);
+ void onRequestCurrentTvInputId(int seq);
+ void onRequestSigning(
+ in String id, in String algorithm, in String alias, in byte[] data, int seq);
+ void onTvAdSessionData(in String type, in Bundle data, int seq);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 9620065..5c5f095 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -31,6 +31,7 @@
*/
interface ITvAdManager {
List<TvAdServiceInfo> getTvAdServiceList(int userId);
+ void sendAppLinkCommand(String serviceId, in Bundle command, int userId);
void createSession(
in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
@@ -58,4 +59,7 @@
int userId);
void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
void removeMediaView(in IBinder sessionToken, int userId);
+
+ void notifyTvInputSessionData(
+ in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 69afb17..eba0bc8 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -46,4 +46,6 @@
void createMediaView(in IBinder windowToken, in Rect frame);
void relayoutMediaView(in Rect frame);
void removeMediaView();
+
+ void notifyTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
index f21ef19..e4e5cbb 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -17,6 +17,7 @@
package android.media.tv.ad;
import android.media.tv.ad.ITvAdSession;
+import android.os.Bundle;
/**
* Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
@@ -26,4 +27,10 @@
oneway interface ITvAdSessionCallback {
void onSessionCreated(in ITvAdSession session);
void onLayoutSurface(int left, int top, int right, int bottom);
+ void onRequestCurrentVideoBounds();
+ void onRequestCurrentChannelUri();
+ void onRequestTrackInfoList();
+ void onRequestCurrentTvInputId();
+ void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+ void onTvAdSessionData(in String type, in Bundle data);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 251351d..e10a20e 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -65,6 +65,7 @@
private static final int DO_SEND_SIGNING_RESULT = 14;
private static final int DO_NOTIFY_ERROR = 15;
private static final int DO_NOTIFY_TV_MESSAGE = 16;
+ private static final int DO_NOTIFY_INPUT_SESSION_DATA = 17;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -180,6 +181,12 @@
args.recycle();
break;
}
+ case DO_NOTIFY_INPUT_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyTvInputSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -280,6 +287,12 @@
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
}
+ @Override
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_INPUT_SESSION_DATA, type, data));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 4dce72f..02c0d75 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -16,12 +16,15 @@
package android.media.tv.ad;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringDef;
import android.annotation.SystemService;
import android.content.Context;
import android.graphics.Rect;
+import android.media.tv.AdBuffer;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.flags.Flags;
@@ -43,7 +46,10 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
@@ -57,6 +63,198 @@
// TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
+ /**
+ * Key for package name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+
+ /**
+ * Key for class name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+
+ /**
+ * Key for command type in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+
+ /**
+ * Key for service ID in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+
+ /**
+ * Key for back URI in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+
+ /**
+ * Broadcast intent action to send app command to TV app.
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String ACTION_APP_LINK_COMMAND =
+ "android.media.tv.ad.action.APP_LINK_COMMAND";
+
+ /**
+ * Intent key for TV input ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+
+ /**
+ * Intent key for TV AD service ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @see TvAdServiceInfo#getId()
+ * @hide
+ */
+ public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+
+ /**
+ * Intent key for TV channel URI. It's used to send app command to TV app.
+ * <p>Type: android.net.Uri
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * Intent key for command type. It's used to send app command to TV app. The value of this key
+ * could vary according to TV apps.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_AD_REQUEST,
+ SESSION_DATA_TYPE_AD_BUFFER_READY,
+ SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST})
+ public @interface SessionDataType {}
+
+ /**
+ * Sends an advertisement request to be processed by the related TV input.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_REQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+
+ /**
+ * Notifies the advertisement buffer is ready.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+
+ /**
+ * Sends request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * Removes request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
+ "remove_broadcast_info_request";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_AD_REQUEST,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_KEY_REQUEST_ID})
+ public @interface SessionDataKey {}
+
+ /**
+ * An object of {@link android.media.tv.AdRequest}.
+ *
+ * <p> Type: android.media.tv.AdRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * An object of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * The ID of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+
private final ITvAdManager mService;
private final int mUserId;
@@ -125,6 +323,79 @@
}
}
+ @Override
+ public void onRequestCurrentVideoBounds(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentVideoBounds();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentChannelUri();
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestTrackInfoList();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentTvInputId();
+ }
+ }
+
+ @Override
+ public void onRequestSigning(
+ String id, String algorithm, String alias, byte[] data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestSigning(id, algorithm, alias, data);
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvAdSessionData(type, data);
+ }
+ }
+
};
ITvAdManagerCallback managerCallback =
@@ -220,6 +491,59 @@
}
/**
+ * Sends app link command.
+ *
+ * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
+ * in {@link TvAdServiceInfo#getId()}.
+ * @param command The command to be sent.
+ * @hide
+ */
+ public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
+ try {
+ mService.sendAppLinkCommand(serviceId, command, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link TvAdServiceCallback}.
+ *
+ * @param callback A callback used to monitor status of the TV AD services.
+ * @param executor A {@link Executor} that the status change will be delivered to.
+ * @hide
+ */
+ public void registerCallback(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mCallbackRecords.add(new TvAdServiceCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link TvAdServiceCallback}.
+ *
+ * @param callback The existing callback to remove.
+ * @hide
+ */
+ public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<TvAdServiceCallbackRecord> it = mCallbackRecords.iterator();
+ it.hasNext(); ) {
+ TvAdServiceCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
* The Session provides the per-session functionality of AD service.
* @hide
*/
@@ -533,6 +857,88 @@
}
}
+ /**
+ * Notifies data from session of linked TvInputService.
+ */
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvInputSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
+ * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
+ * {@code null}.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @hide
+ */
+ public int dispatchInputEvent(@NonNull InputEvent event, Object token,
+ @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
+ Preconditions.checkNotNull(event);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ synchronized (mHandler) {
+ if (mInputChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mEventToken = token;
+ p.mCallback = callback;
+ p.mEventHandler = handler;
+ return p;
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token A token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
@@ -639,23 +1045,6 @@
mPendingEventPool.release(p);
}
- /**
- * Callback that is invoked when an input event that was dispatched to this session has been
- * finished.
- *
- * @hide
- */
- public interface FinishedInputEventCallback {
- /**
- * Called when the dispatched input event is finished.
- *
- * @param token A token passed to {@link #dispatchInputEvent}.
- * @param handled {@code true} if the dispatched input event was handled properly.
- * {@code false} otherwise.
- */
- void onFinishedInputEvent(Object token, boolean handled);
- }
-
private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -730,6 +1119,58 @@
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
}
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentVideoBounds(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentChannelUri(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestTrackInfoList(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentTvInputId(Session session) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ */
+ public void onRequestSigning(
+ Session session, String signingId, String algorithm, String alias, byte[] data) {
+ }
+
}
/**
@@ -810,6 +1251,62 @@
}
});
}
+
+ void postRequestCurrentVideoBounds() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentVideoBounds(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentChannelUri() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentChannelUri(mSession);
+ }
+ });
+ }
+
+ void postRequestTrackInfoList() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestTrackInfoList(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentTvInputId() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentTvInputId(mSession);
+ }
+ });
+ }
+
+ void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
+ }
+ });
+ }
+
+ void postTvAdSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getInputSession() != null) {
+ mSession.getInputSession().notifyTvAdSessionData(type, data);
+ }
+ }
+ });
+ }
}
private static final class TvAdServiceCallbackRecord {
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 5d81837..4d8f5c8b 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -132,6 +133,9 @@
/**
* Called when app link command is received.
+ *
+ * @see TvAdManager#sendAppLinkCommand(String, Bundle)
+ * @hide
*/
public void onAppLinkCommand(@NonNull Bundle command) {
}
@@ -279,6 +283,143 @@
onResetAdService();
}
+ /**
+ * Requests the bounds of the current video.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentVideoBounds() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentVideoBounds");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentVideoBounds();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentVideoBounds", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the URI of the current channel.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentChannelUri() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentChannelUri");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentChannelUri();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentChannelUri", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the list of {@link TvTrackInfo}.
+ * @hide
+ */
+ @CallSuper
+ public void requestTrackInfoList() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestTrackInfoList");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestTrackInfoList();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestTrackInfoList", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests current TV input ID.
+ *
+ * @see android.media.tv.TvInputInfo
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentTvInputId() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentTvInputId");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentTvInputId();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentTvInputId", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests signing of the given data.
+ *
+ * <p>This is used when the corresponding server of the AD service app requires signing
+ * during handshaking, and the service doesn't have the built-in private key. The private
+ * key is provided by the content providers and pre-built in the related app, such as TV
+ * app.
+ *
+ * @param signingId the ID to identify the request. When a result is received, this ID can
+ * be used to correlate the result with the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc. The name is from standards like
+ * FIPS PUB 186-4 and PKCS #1.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ * @see #onSigningResult(String, byte[])
+ */
+ @CallSuper
+ public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
+ @NonNull String alias, @NonNull byte[] data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestSigning");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestSigning", e);
+ }
+ }
+ });
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -470,6 +611,19 @@
}
/**
+ * Called when data from the linked {@link android.media.tv.TvInputService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
+ /**
* Called when the size of the media view is changed by the application.
*
* <p>This is always called at least once when the session is created regardless of whether
@@ -497,6 +651,33 @@
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.TvInputService} object via TvView.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @see TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void notifyTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvAdSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -594,6 +775,10 @@
onTvMessage(type, data);
}
+ void notifyTvInputSessionData(String type, Bundle data) {
+ onTvInputSessionData(type, data);
+ }
+
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index ec23b7c..604dbd5 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -28,17 +28,21 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import java.util.List;
import java.util.concurrent.Executor;
@@ -88,6 +92,7 @@
private boolean mMediaViewCreated;
private Rect mMediaViewFrame;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
@@ -327,6 +332,109 @@
mSession.dispatchSurfaceChanged(format, width, height);
}
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled="
+ + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchUnhandledInputEvent(event);
+ }
+ }
+ };
+
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ *
+ * It gives the host application a chance to dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ * @hide
+ */
+ public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event also has not been handled by the user provided
+ * callback. This is the last chance to handle the unhandled input event in the
+ * TvAdView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Sets a listener to be invoked when an input event is not handled
+ * by the TV AD service.
+ *
+ * @param listener The callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public void setOnUnhandledInputEventListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ // TODO: handle CallbackExecutor
+ }
+
+ /**
+ * Gets the {@link OnUnhandledInputEventListener}.
+ * <p>Returns {@code null} if the listener is not set or is cleared.
+ *
+ * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
+ * @see #clearOnUnhandledInputEventListener()
+ * @hide
+ */
+ @Nullable
+ public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
+ return mOnUnhandledInputEventListener;
+ }
+
+ /**
+ * Clears the {@link OnUnhandledInputEventListener}.
+ * @hide
+ */
+ public void clearOnUnhandledInputEventListener() {
+ mOnUnhandledInputEventListener = null;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != TvAdManager.Session.DISPATCH_NOT_HANDLED;
+ }
+
/**
* Prepares the AD service of corresponding {@link TvAdService}.
*
@@ -504,6 +612,24 @@
}
/**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the TV AD service.
+ *
+ * <p>This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(@NonNull InputEvent event);
+ }
+
+ /**
* Sets the callback to be invoked when an event is dispatched to this TvAdView.
*
* @param callback the callback to receive events. MUST NOT be {@code null}.
@@ -606,6 +732,117 @@
mUseRequestedSurfaceLayout = true;
requestLayout();
}
+
+ @Override
+ public void onRequestCurrentVideoBounds(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentVideoBounds - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentVideoBounds(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentChannelUri - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentChannelUri(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestTrackInfoList");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestTrackInfoList - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestTrackInfoList(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentTvInputId - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentTvInputId(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestSigning(TvAdManager.Session session, String id, String algorithm,
+ String alias, byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSigning");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSigning - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestSigning(mServiceId, id, algorithm, alias, data);
+ }
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -613,5 +850,55 @@
* @hide
*/
public abstract static class TvAdCallback {
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds()}
+ * is called.
+ *
+ * @param serviceId The ID of the TV AD service bound to this view.
+ */
+ public void onRequestCurrentVideoBounds(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri()} is
+ * called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentChannelUri(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestTrackInfoList(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentTvInputId(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ */
+ public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId,
+ @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
+ }
}
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3524f8c..f8b640c7 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -17,7 +17,7 @@
method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderModePollingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 11eb97b..4d56c11 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1764,7 +1764,9 @@
private static final int ENABLE_POLLING_FLAGS = 0x0000;
/**
- * Privileged API to enable disable reader polling.
+ * Privileged API to enable or disable reader polling.
+ * Unlike {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}, this API does not
+ * need a foreground activity to control reader mode parameters
* Note: Use with caution! The app is responsible for ensuring that the polling state is
* returned to normal.
*
@@ -1778,14 +1780,14 @@
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@SuppressLint("VisiblySynchronized")
- public void setReaderMode(boolean enablePolling) {
+ public void setReaderModePollingEnabled(boolean enable) {
synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
}
Binder token = new Binder();
- int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
try {
NfcAdapter.sService.setReaderMode(token, null, flags, null);
} catch (RemoteException e) {
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
index 7b235f8..0100718 100644
--- a/packages/CredentialManager/res/drawable/more_horiz_24px.xml
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -3,6 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
+ android:contentDescription="@string/more_options_content_description"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 929756c..fdda9ea 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -27,7 +28,7 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:contentDescription="@string/provider_icon_content_description"
+ android:contentDescription="@string/more_options_content_description"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 1fe5e0e..c7c2fda 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -24,7 +25,6 @@
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 3a8c78f..53852cb 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -27,4 +27,5 @@
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
<integer name="autofill_max_visible_datasets">3</integer>
+ <dimen name="dropdown_touch_target_min_width">48dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index f98164b..82b47a9 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -172,5 +172,5 @@
<!-- Strings for dropdown presentation. -->
<!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
<string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
- <string name="provider_icon_content_description">Credential provider icon</string>
+ <string name="more_options_content_description">More</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 68f1c86..02afc54 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -74,6 +74,8 @@
setMaxHeightMethodName,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
+ remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
+ .providerDisplayName);
val drawableId =
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
remoteViews.setInt(
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 9419771..f5741a0 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
-ioffe@google.com
-toddke@google.com
-patb@google.com
\ No newline at end of file
+include /PACKAGE_MANAGER_OWNERS
+ioffe@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 5a21d59..09e0d61 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -75,19 +75,15 @@
</intent-filter>
</activity>
- <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem
- is resolved for new implementation -->
<activity android:name=".InstallStaging"
- android:theme="@style/Theme.AlertDialogActivity.NoDim"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".DeleteStagedFileOnResult"
android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".InstallInstalling"
android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 2736870..acbdff8 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
-svetoslavganov@google.com
+# Bug component: 1002434
include /PACKAGE_MANAGER_OWNERS
-# For automotive related changes
-rogerxue@google.com
+sumedhsen@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 811fa73..9a06229 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,9 +32,4 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="Theme.AlertDialogActivity.NoDim"
- parent="@style/Theme.AlertDialogActivity.NoActionBar">
- <item name="android:backgroundDimAmount">0</item>
- </style>
-
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index ceb580d..7dc157f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -308,7 +308,6 @@
}
private void initiateInstall() {
- bindUi();
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
@@ -448,6 +447,7 @@
if (mAppSnippet != null) {
// load placeholder layout with OK button disabled until we override this layout in
// startInstallConfirm
+ bindUi();
checkIfAllowedAndInitiateInstall();
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b2a8b87..960ebcc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -21,6 +21,7 @@
data class CardButton(
val text: String,
+ val contentDescription: String? = null,
val onClick: () -> Unit,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index c7845fa..700fa48 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -45,6 +45,8 @@
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -182,7 +184,11 @@
@Composable
private fun Button(button: CardButton, color: Color) {
- TextButton(onClick = button.onClick) {
+ TextButton(
+ onClick = button.onClick,
+ modifier =
+ Modifier.semantics { button.contentDescription?.let { this.contentDescription = it } }
+ ) {
Text(text = button.text, color = color)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index beb9433..b5b2525 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -36,8 +36,7 @@
@RunWith(AndroidJUnit4::class)
class SettingsCardTest {
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@@ -76,9 +75,7 @@
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) {}
- ),
+ buttons = listOf(CardButton(text = TEXT) {}),
)
)
}
@@ -94,9 +91,7 @@
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) { buttonClicked = true }
- ),
+ buttons = listOf(CardButton(text = TEXT) { buttonClicked = true }),
)
)
}
@@ -107,6 +102,25 @@
}
@Test
+ fun settingsCard_buttonHaveContentDescription() {
+ composeTestRule.setContent {
+ SettingsCard(
+ CardModel(
+ title = "",
+ text = "",
+ buttons = listOf(CardButton(
+ text = TEXT,
+ contentDescription = CONTENT_DESCRIPTION,
+ ) {}
+ ),
+ )
+ )
+ }
+
+ composeTestRule.onNodeWithContentDescription(CONTENT_DESCRIPTION).assertIsDisplayed()
+ }
+
+ @Test
fun settingsCard_dismiss() {
composeTestRule.setContent {
var isVisible by remember { mutableStateOf(true) }
@@ -130,5 +144,6 @@
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
+ const val CONTENT_DESCRIPTION = "content-description"
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 38ec931..4c255a5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -107,6 +107,7 @@
Settings.System.TOUCHPAD_POINTER_SPEED,
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
+ Settings.System.TOUCHPAD_TAP_DRAGGING,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
Settings.System.CAMERA_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 98941c7..011b42f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -209,6 +209,7 @@
VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1670a70..1e146a5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -3002,6 +3002,9 @@
dumpSetting(s, p,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
SystemSettingsProto.Touchpad.TAP_TO_CLICK);
+ dumpSetting(s, p,
+ Settings.System.TOUCHPAD_TAP_DRAGGING,
+ SystemSettingsProto.Touchpad.TAP_DRAGGING);
p.end(touchpadToken);
dumpSetting(s, p,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 54ab5d1..0050676 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1083,5 +1083,25 @@
<!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
<property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
android:value="@xml/self_certified_network_capabilities_both" />
+
+
+ <service
+ android:name="com.android.systemui.dreams.homecontrols.HomeControlsDreamService"
+ android:exported="false"
+ android:enabled="false"
+ android:label="@string/home_controls_dream_label"
+ android:description="@string/home_controls_dream_description"
+ android:permission="android.permission.BIND_DREAM_SERVICE"
+ android:icon="@drawable/controls_icon"
+ >
+
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/home_controls_dream_metadata" />
+ </service>
</application>
</manifest>
diff --git a/packages/SystemUI/aconfig/cross_device_control.aconfig b/packages/SystemUI/aconfig/cross_device_control.aconfig
new file mode 100644
index 0000000..d3f14c1
--- /dev/null
+++ b/packages/SystemUI/aconfig/cross_device_control.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.systemui"
+
+flag {
+ name: "legacy_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the legacy le audio sharing UI."
+ bug: "322295262"
+}
+
+flag {
+ name: "enable_personal_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the personal le audio sharing UI in UMO."
+ bug: "322295480"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 375fe13..3db99f28 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -59,14 +59,6 @@
}
flag {
- name: "notification_lifetime_extension_refactor"
- namespace: "systemui"
- description: "Enables moving notification lifetime extension management from SystemUI to "
- "Notification Manager Service"
- bug: "299448097"
-}
-
-flag {
name: "notifications_live_data_store_refactor"
namespace: "systemui"
description: "Replaces NotifLiveDataStore with ActiveNotificationListRepository, and updates consumers. "
@@ -378,3 +370,17 @@
description: "Enables the compose version of keyguard."
bug: "301968149"
}
+
+flag {
+ name: "enable_contextual_tip_for_power_off"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to power off or restart phone"
+ bug: "322891421"
+}
+
+flag {
+ name: "enable_contextual_tip_for_take_screenshot"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to take screenshot."
+ bug: "322891421"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index f9b91cf..bd539a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
@@ -36,16 +37,21 @@
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onInterceptKeyBeforeSoftKeyboard
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@Composable
@@ -64,6 +70,7 @@
val password: String by viewModel.password.collectAsState()
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+ val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -116,5 +123,28 @@
false
}
},
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else null
+ )
+}
+
+/** Button for changing the password input method (IME). */
+@Composable
+private fun ImeSwitcherButton(
+ viewModel: PasswordBouncerViewModel,
+ color: Color,
+) {
+ val context = LocalContext.current
+ PlatformIconButton(
+ onClick = { viewModel.onImeSwitcherButtonClicked(context.displayId) },
+ iconResource = R.drawable.ic_lockscreen_ime,
+ contentDescription = stringResource(R.string.accessibility_ime_switch_button),
+ colors =
+ IconButtonDefaults.filledIconButtonColors(
+ contentColor = color,
+ containerColor = Color.Transparent,
+ )
)
}
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 ff5a698..92bc1f1 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
@@ -124,7 +124,7 @@
// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
- return SceneKey(name = toString(), identity = this)
+ return SceneKey(debugName = toString(), identity = this)
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 42fcd13..5e27d82 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -21,19 +21,27 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.notifications.ui.composable.NotificationStack
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+@SysUISingleton
class NotificationSection
@Inject
constructor(
@@ -42,22 +50,41 @@
controller: NotificationStackScrollLayoutController,
sceneContainerFlags: SceneContainerFlags,
sharedNotificationContainer: SharedNotificationContainer,
+ sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
ambientState: AmbientState,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
+ @Main mainDispatcher: CoroutineDispatcher,
) {
init {
- if (sceneContainerFlags.flexiNotifsEnabled()) {
+ if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
+ // This scene container section moves the NSSL to the SharedNotificationContainer. This
+ // also requires that SharedNotificationContainer gets moved to the SceneWindowRootView
+ // by the SceneWindowRootViewBinder.
+ // Prior to Scene Container, but when the KeyguardShadeMigrationNssl flag is enabled,
+ // NSSL is moved into this container by the NotificationStackScrollLayoutSection.
(stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
- NotificationStackAppearanceViewBinder.bind(
- context,
+ SharedNotificationContainerBinder.bind(
sharedNotificationContainer,
- notificationStackAppearanceViewModel,
- ambientState,
+ sharedNotificationContainerViewModel,
+ sceneContainerFlags,
controller,
+ notificationStackSizeCalculator,
+ mainDispatcher,
)
+
+ if (sceneContainerFlags.flexiNotifsEnabled()) {
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
new file mode 100644
index 0000000..2ba78cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [scrimOffset]** back to the [minScrimOffset] and
+ * then allows scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ * then resets the [scrimOffset] to [maxScrimOffset].
+ */
+fun NotificationScrimNestedScrollConnection(
+ scrimOffset: () -> Float,
+ onScrimOffsetChanged: (Float) -> Unit,
+ minScrimOffset: () -> Float,
+ maxScrimOffset: Float,
+ contentHeight: () -> Float,
+ minVisibleScrimHeight: () -> Float,
+): PriorityNestedScrollConnection {
+ return PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
+ // scrolling up and inner content is taller than the scrim, so scrim needs to
+ // expand; content can scroll once scrim is at the minScrimOffset.
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+ offsetAvailable < 0 &&
+ offsetBeforeStart == 0f &&
+ contentHeight() > minVisibleScrimHeight() &&
+ scrimOffset() > minScrimOffset()
+ },
+ // scrolling down and content is done scrolling to top. After that, the scrim
+ // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
+ canStartPostScroll = { offsetAvailable, _ ->
+ offsetAvailable > 0 && scrimOffset() < maxScrimOffset
+ },
+ canStartPostFling = { false },
+ canContinueScroll = {
+ val currentHeight = scrimOffset()
+ minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
+ },
+ canScrollOnFling = true,
+ onStart = { /* do nothing */},
+ onScroll = { offsetAvailable ->
+ val currentHeight = scrimOffset()
+ val amountConsumed =
+ if (offsetAvailable > 0) {
+ val amountLeft = maxScrimOffset - currentHeight
+ offsetAvailable.coerceAtMost(amountLeft)
+ } else {
+ val amountLeft = minScrimOffset() - currentHeight
+ offsetAvailable.coerceAtLeast(amountLeft)
+ }
+ onScrimOffsetChanged(currentHeight + amountConsumed)
+ amountConsumed
+ },
+ // Don't consume the velocity on pre/post fling
+ onStop = { 0f },
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index e835d3e..0e08a19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -20,32 +20,53 @@
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.scene.ui.composable.Gone
+import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -100,33 +121,109 @@
@Composable
fun SceneScope.NotificationScrollingStack(
viewModel: NotificationsPlaceholderViewModel,
+ maxScrimTop: () -> Float,
modifier: Modifier = Modifier,
) {
+ val density = LocalDensity.current
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
-
- val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
-
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
- Box(
- modifier =
- modifier
- .verticalNestedScrollToScene()
- .fillMaxWidth()
- .element(Notifications.Elements.NotificationScrim)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = expansionFraction
- }
- .background(MaterialTheme.colorScheme.surface)
- .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
- ) {
- NotificationPlaceholder(
- viewModel = viewModel,
- form = Form.Stack,
- modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
+ val navBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
+ val statusBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
+ val displayCutoutHeight =
+ with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val screenHeight =
+ with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
+ navBarHeight +
+ maxOf(statusBarHeight, displayCutoutHeight)
+
+ val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
+
+ // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
+ // calculated in minScrimOffset. The scrim is the same height as the screen minus the
+ // height of the Shade Header, and at rest (scrimOffset = 0) its top bound is at maxScrimStartY.
+ // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
+ // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
+ // entire height of the scrim is visible on screen.
+ val scrimOffset = remember { mutableStateOf(0f) }
+
+ val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() }
+
+ // The minimum offset for the scrim. The scrim is considered fully expanded when it
+ // is at this offset.
+ val minScrimOffset: () -> Float = { minScrimTop - maxScrimTop() }
+
+ // The height of the scrim visible on screen when it is in its resting (collapsed) state.
+ val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+
+ // we are not scrolled to the top unless the scrim is at its maximum offset.
+ LaunchedEffect(viewModel, scrimOffset) {
+ snapshotFlow { scrimOffset.value >= 0f }
+ .collect { isScrolledToTop -> viewModel.setScrolledToTop(isScrolledToTop) }
+ }
+
+ // if contentHeight drops below minimum visible scrim height while scrim is
+ // expanded, reset scrim offset.
+ LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) {
+ snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
+ }
+
+ Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ }
+ .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
)
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha =
+ if (layoutState.isTransitioningBetween(Gone, Shade)) {
+ (expansionFraction / 0.3f).coerceAtMost(1f)
+ } else 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier =
+ Modifier.verticalNestedScrollToScene(
+ topBehavior = NestedScrollBehavior.EdgeWithPreview,
+ )
+ .nestedScroll(
+ remember(
+ scrimOffset,
+ maxScrimTop,
+ minScrimTop,
+ ) {
+ NotificationScrimNestedScrollConnection(
+ scrimOffset = { scrimOffset.value },
+ onScrimOffsetChanged = { scrimOffset.value = it },
+ minScrimOffset = minScrimOffset,
+ maxScrimOffset = 0f,
+ contentHeight = { contentHeight.value },
+ minVisibleScrimHeight = minVisibleScrimHeight,
+ )
+ }
+ )
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth()
+ .height { (contentHeight.value + navBarHeight).roundToInt() },
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index bbfe0fd..5531f9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -46,6 +47,11 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -56,7 +62,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -116,6 +122,8 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
+
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -234,10 +242,32 @@
}
}
}
- HeadsUpNotificationSpace(
- viewModel = viewModel.notifications,
- isPeekFromBottom = true,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
+ // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
+ // transition from Shade scene.
+ Box(
+ modifier =
+ Modifier.element(Notifications.Elements.NotificationScrim)
+ .fillMaxWidth()
+ .height(0.dp)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .align(Alignment.BottomCenter)
+ .onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.notifications.onContentTopChanged(
+ coordinates.positionInWindow().y
+ )
+ val boundsInWindow = coordinates.boundsInWindow()
+ viewModel.notifications.onBoundsChanged(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ }
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 747faab..770d654 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -18,13 +18,10 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
@@ -66,12 +63,6 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = modifier) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
- HeadsUpNotificationSpace(
- viewModel = notificationsViewModel,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
- )
- }
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
index 5336bf6..0c66701 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -12,5 +12,5 @@
// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
- return SceneTransitionSceneKey(name = toString(), identity = this)
+ return SceneTransitionSceneKey(debugName = toString(), identity = this)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 1545372..497fe87 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -32,6 +32,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
@@ -62,6 +63,7 @@
import com.android.systemui.util.animation.MeasurementInput
import javax.inject.Inject
import javax.inject.Named
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -154,62 +156,104 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
- val localDensity = LocalDensity.current
+ val density = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
+ val maxNotifScrimTop = remember { mutableStateOf(0f) }
Box(
modifier =
modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
)
Box {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
- ) {
- CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
- )
- QuickSettings(
- modifier = Modifier.height(130.dp),
- viewModel.qsSceneAdapter,
- )
-
- if (viewModel.isMediaVisible()) {
- val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
- MediaCarousel(
- modifier =
- Modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints
- ->
- val placeable = measurable.measure(constraints)
-
- // Notify controller to size the carousel for the current space
- mediaHost.measurementInput =
- MeasurementInput(placeable.width, placeable.height)
- mediaCarouselController.setSceneContainerSize(
- placeable.width,
- placeable.height
+ Layout(
+ contents =
+ listOf(
+ {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ ) {
+ CollapsedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier =
+ Modifier.padding(
+ horizontal = Shade.Dimensions.HorizontalPadding
+ )
+ )
+ QuickSettings(
+ modifier = Modifier.height(130.dp),
+ viewModel.qsSceneAdapter,
)
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(0, 0)
- }
- },
- mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
- layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
- carouselController = mediaCarouselController,
- )
- }
+ if (viewModel.isMediaVisible()) {
+ val mediaHeight =
+ dimensionResource(R.dimen.qs_media_session_height_expanded)
+ MediaCarousel(
+ modifier =
+ Modifier.height(mediaHeight).fillMaxWidth().layout {
+ measurable,
+ constraints ->
+ val placeable = measurable.measure(constraints)
- Spacer(modifier = Modifier.height(16.dp))
- NotificationScrollingStack(
- viewModel = viewModel.notifications,
- modifier = Modifier.fillMaxWidth().weight(1f),
- )
+ // Notify controller to size the carousel for the
+ // current space
+ mediaHost.measurementInput =
+ MeasurementInput(placeable.width, placeable.height)
+ mediaCarouselController.setSceneContainerSize(
+ placeable.width,
+ placeable.height
+ )
+
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ },
+ mediaHost = mediaHost,
+ layoutWidth = layoutWidth.value,
+ layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ },
+ {
+ NotificationScrollingStack(
+ viewModel = viewModel.notifications,
+ maxScrimTop = { maxNotifScrimTop.value },
+ )
+ },
+ )
+ ) { measurables, constraints ->
+ check(measurables.size == 2)
+ check(measurables[0].size == 1)
+ check(measurables[1].size == 1)
+
+ val quickSettingsPlaceable = measurables[0][0].measure(constraints)
+
+ val notificationsMeasurable = measurables[1][0]
+ val notificationsScrimMaxHeight =
+ constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val notificationsPlaceable =
+ notificationsMeasurable.measure(
+ constraints.copy(
+ minHeight = notificationsScrimMaxHeight,
+ maxHeight = notificationsScrimMaxHeight
+ )
+ )
+
+ maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ quickSettingsPlaceable.placeRelative(x = 0, y = 0)
+ notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 7d3b0fb..b9b472f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -30,6 +30,7 @@
internal fun CoroutineScope.animateToScene(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
val transitionState = layoutState.transitionState
if (transitionState.currentScene == target) {
@@ -45,7 +46,7 @@
}
return when (transitionState) {
- is TransitionState.Idle -> animate(layoutState, target)
+ is TransitionState.Idle -> animate(layoutState, target, transitionKey)
is TransitionState.Transition -> {
// A transition is currently running: first check whether `transition.toScene` or
// `transition.fromScene` is the same as our target scene, in which case the transition
@@ -61,13 +62,13 @@
// The transition is already finished (progress ~= 1): no need to animate. We
// finish the current transition early to make sure that the current state
// change is committed.
- layoutState.finishTransition(transitionState, transitionState.currentScene)
+ layoutState.finishTransition(transitionState, target)
null
} else {
// The transition is in progress: start the canned animation at the same
// progress as it was in.
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress)
+ animate(layoutState, target, transitionKey, startProgress = progress)
}
} else if (transitionState.fromScene == target) {
// There is a transition from [target] to another scene: simply animate the same
@@ -78,16 +79,22 @@
if (progress.absoluteValue < ProgressVisibilityThreshold) {
// The transition is at progress ~= 0: no need to animate.We finish the current
// transition early to make sure that the current state change is committed.
- layoutState.finishTransition(transitionState, transitionState.currentScene)
+ layoutState.finishTransition(transitionState, target)
null
} else {
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress, reversed = true)
+ animate(
+ layoutState,
+ target,
+ transitionKey,
+ startProgress = progress,
+ reversed = true,
+ )
}
} else {
// Generic interruption; the current transition is neither from or to [target].
// TODO(b/290930950): Better handle interruptions here.
- animate(layoutState, target)
+ animate(layoutState, target, transitionKey)
}
}
}
@@ -96,6 +103,7 @@
private fun CoroutineScope.animate(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
startProgress: Float = 0f,
reversed: Boolean = false,
): TransitionState.Transition {
@@ -127,7 +135,7 @@
// Change the current layout state to start this new transition. This will compute the
// TransformationSpec associated to this transition, which we need to initialize the Animatable
// that will actually animate it.
- layoutState.startTransition(transition)
+ layoutState.startTransition(transition, transitionKey)
// The transformation now contains the spec that we should use to instantiate the Animatable.
val animationSpec = layoutState.transformationSpec.progressSpec
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 9d4b69c..2596d4a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -42,19 +42,20 @@
/** Key for a scene. */
class SceneKey(
- name: String,
+ debugName: String,
identity: Any = Object(),
-) : Key(name, identity), UserActionResult {
+) : Key(debugName, identity), UserActionResult {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
// access internal members.
- val testTag: String = "scene:$name"
+ val testTag: String = "scene:$debugName"
/** The unique [ElementKey] identifying this scene's root element. */
- val rootElementKey = ElementKey(name, identity)
+ val rootElementKey = ElementKey(debugName, identity)
// Implementation of [UserActionResult].
override val toScene: SceneKey = this
+ override val transitionKey: TransitionKey? = null
override val distance: UserActionDistance? = null
override fun toString(): String {
@@ -64,7 +65,7 @@
/** Key for an element. */
class ElementKey(
- name: String,
+ debugName: String,
identity: Any = Object(),
/**
@@ -72,11 +73,11 @@
* or compose MovableElements.
*/
val scenePicker: ElementScenePicker = DefaultElementScenePicker,
-) : Key(name, identity), ElementMatcher {
+) : Key(debugName, identity), ElementMatcher {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
// access internal members.
- val testTag: String = "element:$name"
+ val testTag: String = "element:$debugName"
override fun matches(key: ElementKey, scene: SceneKey): Boolean {
return key == this
@@ -99,8 +100,18 @@
}
/** Key for a shared value of an element. */
-class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) {
+class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
override fun toString(): String {
return "ValueKey(debugName=$debugName)"
}
}
+
+/**
+ * Key for a transition. This can be used to specify which transition spec should be used when
+ * starting the transition between two scenes.
+ */
+class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
+ override fun toString(): String {
+ return "TransitionKey(debugName=$debugName)"
+ }
+}
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
index 5f615fd..529fc03 100644
--- 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
@@ -263,10 +263,10 @@
val deltaOffset = drag.position - initialDown.position
val delta =
when (orientation) {
- Orientation.Horizontal -> deltaOffset.y
+ Orientation.Horizontal -> deltaOffset.x
Orientation.Vertical -> deltaOffset.y
}
- check(delta != 0f)
+ check(delta != 0f) { "delta is equal to 0" }
overSlop = delta.sign
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 58c3be24..23af5ac 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -54,7 +54,7 @@
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
if (isDrivingTransition || force) {
- layoutState.startTransition(newTransition)
+ layoutState.startTransition(newTransition, newTransition.key)
// Initialize SwipeTransition.swipeSpec. Note that this must be called right after
// layoutState.startTransition() is called, because it computes the
@@ -146,9 +146,10 @@
val fromScene = layoutImpl.scene(transitionState.currentScene)
updateSwipes(fromScene, startedPosition, pointersDown)
- val (targetScene, distance) =
- findTargetSceneAndDistance(fromScene, overSlop, updateSwipesResults = true) ?: return
- updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
+ val result =
+ findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true)
+ ?: return
+ updateTransition(SwipeTransition(fromScene, result), force = true)
}
private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
@@ -224,8 +225,8 @@
computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
val isNewFromScene = fromScene.key != swipeTransition.fromScene
- val (targetScene, distance) =
- findTargetSceneAndDistance(
+ val result =
+ findUserActionResult(
fromScene,
swipeTransition.dragOffset,
updateSwipesResults = isNewFromScene,
@@ -236,9 +237,13 @@
}
swipeTransition.dragOffset += acceleratedOffset
- if (isNewFromScene || targetScene.key != swipeTransition.toScene) {
+ if (
+ isNewFromScene ||
+ result.toScene != swipeTransition.toScene ||
+ result.transitionKey != swipeTransition.key
+ ) {
updateTransition(
- SwipeTransition(fromScene, targetScene, distance).apply {
+ SwipeTransition(fromScene, result).apply {
this.dragOffset = swipeTransition.dragOffset
}
)
@@ -306,7 +311,7 @@
}
/**
- * Returns the target scene and distance from [fromScene] in the direction [directionOffset].
+ * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
*
* @param fromScene the scene from which we look for the target
* @param directionOffset signed float that indicates the direction. Positive is down or right
@@ -322,60 +327,45 @@
* [directionOffset] is 0f and both direction are available, it will default to
* [upOrLeftResult].
*/
- private inline fun findTargetSceneAndDistance(
+ private fun findUserActionResult(
fromScene: Scene,
directionOffset: Float,
updateSwipesResults: Boolean,
- ): Pair<Scene, Float>? {
+ ): UserActionResult? {
if (updateSwipesResults) updateSwipesResults(fromScene)
- // Compute the target scene depending on the current offset.
return when {
upOrLeftResult == null && downOrRightResult == null -> null
(directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
- upOrLeftResult?.let { result ->
- Pair(
- layoutImpl.scene(result.toScene),
- -fromScene.getAbsoluteDistance(result.distance)
- )
- }
- else ->
- downOrRightResult?.let { result ->
- Pair(
- layoutImpl.scene(result.toScene),
- fromScene.getAbsoluteDistance(result.distance)
- )
- }
+ upOrLeftResult
+ else -> downOrRightResult
}
}
/**
- * A strict version of [findTargetSceneAndDistance] that will return null when there is no Scene
- * in [directionOffset] direction
+ * A strict version of [findUserActionResult] that will return null when there is no Scene in
+ * [directionOffset] direction
*/
- private inline fun findTargetSceneAndDistanceStrict(
- fromScene: Scene,
- directionOffset: Float,
- ): Pair<Scene, Float>? {
+ private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
return when {
- directionOffset > 0f ->
- upOrLeftResult?.let { result ->
- Pair(
- layoutImpl.scene(result.toScene),
- -fromScene.getAbsoluteDistance(result.distance),
- )
- }
- directionOffset < 0f ->
- downOrRightResult?.let { result ->
- Pair(
- layoutImpl.scene(result.toScene),
- fromScene.getAbsoluteDistance(result.distance),
- )
- }
+ directionOffset > 0f -> upOrLeftResult
+ directionOffset < 0f -> downOrRightResult
else -> null
}
}
+ private fun computeAbsoluteDistance(
+ fromScene: Scene,
+ result: UserActionResult,
+ ): Float {
+ return if (result == upOrLeftResult) {
+ -fromScene.getAbsoluteDistance(result.distance)
+ } else {
+ check(result == downOrRightResult)
+ fromScene.getAbsoluteDistance(result.distance)
+ }
+ }
+
internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition) {
@@ -440,8 +430,8 @@
if (startFromIdlePosition) {
// If there is a target scene, we start the overscroll animation.
- val (targetScene, distance) =
- findTargetSceneAndDistanceStrict(fromScene, velocity)
+ val result =
+ findUserActionResultStrict(velocity)
?: run {
// We will not animate
layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
@@ -449,7 +439,7 @@
}
updateTransition(
- SwipeTransition(fromScene, targetScene, distance).apply {
+ SwipeTransition(fromScene, result).apply {
_currentScene = swipeTransition._currentScene
}
)
@@ -496,7 +486,17 @@
}
}
+ private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
+ return SwipeTransition(
+ result.transitionKey,
+ fromScene,
+ layoutImpl.scene(result.toScene),
+ computeAbsoluteDistance(fromScene, result),
+ )
+ }
+
internal class SwipeTransition(
+ val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
/**
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 7e0aa9c3..5258078 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
@@ -388,8 +388,9 @@
/**
* The result of performing a [UserAction].
*
- * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
- * easily create a [UserActionResult] with a fixed distance:
+ * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
+ * when defining your [UserActionResult]s.
+ *
* ```
* SceneTransitionLayout(...) {
* scene(
@@ -397,7 +398,7 @@
* userActions =
* mapOf(
* Swipe.Right to Scene.Bar,
- * Swipe.Down to Scene.Doe withDistance 100.dp,
+ * Swipe.Down to Scene.Doe,
* )
* )
* ) { ... }
@@ -408,6 +409,9 @@
/** The scene we should be transitioning to during the [UserAction]. */
val toScene: SceneKey
+ /** The key of the transition that should be used. */
+ val transitionKey: TransitionKey?
+
/**
* The distance the action takes to animate from 0% to 100%.
*
@@ -416,6 +420,32 @@
val distance: UserActionDistance?
}
+/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: UserActionDistance? = null,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return object : UserActionResult {
+ override val toScene: SceneKey = toScene
+ override val transitionKey: TransitionKey? = transitionKey
+ override val distance: UserActionDistance? = distance
+ }
+}
+
+/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: Dp,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return UserActionResult(
+ toScene = toScene,
+ distance = FixedDistance(distance),
+ transitionKey = transitionKey,
+ )
+}
+
interface UserActionDistance {
/**
* Return the **absolute** distance of the user action given the size of the scene we are
@@ -424,22 +454,6 @@
fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
}
-/**
- * A utility function to make it possible to define user actions with a distance using the syntax
- * `Swipe.Up to Scene.foo withDistance 100.dp`
- */
-infix fun Pair<UserAction, SceneKey>.withDistance(
- distance: Dp
-): Pair<UserAction, UserActionResult> {
- val scene = second
- val distance = FixedDistance(distance)
- return first to
- object : UserActionResult {
- override val toScene: SceneKey = scene
- override val distance: UserActionDistance = distance
- }
-}
-
/** The user action has a fixed [absoluteDistance]. */
private class FixedDistance(private val distance: Dp) : UserActionDistance {
override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 956e326..aee6f9e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -92,6 +92,7 @@
fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
): TransitionState.Transition?
}
@@ -213,11 +214,14 @@
}
/** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
- internal fun startTransition(transition: TransitionState.Transition) {
+ internal fun startTransition(
+ transition: TransitionState.Transition,
+ transitionKey: TransitionKey?,
+ ) {
// Compute the [TransformationSpec] when the transition starts.
transformationSpec =
transitions
- .transitionSpec(transition.fromScene, transition.toScene)
+ .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
transitionState = transition
@@ -265,7 +269,11 @@
// Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
- animateToScene(layoutState = this@HoistedSceneTransitionLayoutScene, newKey)
+ animateToScene(
+ layoutState = this@HoistedSceneTransitionLayoutScene,
+ target = newKey,
+ transitionKey = null,
+ )
}
}
}
@@ -278,14 +286,14 @@
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
override fun setTargetScene(
targetScene: SceneKey,
- coroutineScope: CoroutineScope
+ coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
- return with(this) {
- coroutineScope.animateToScene(
- layoutState = this@MutableSceneTransitionLayoutStateImpl,
- target = targetScene,
- )
- }
+ return coroutineScope.animateToScene(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ target = targetScene,
+ transitionKey = transitionKey,
+ )
}
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
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 ac423b7..b8f9359 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
@@ -42,32 +42,42 @@
internal val defaultSwipeSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
) {
- private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
+ private val cache =
+ mutableMapOf<
+ SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ >()
- internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
+ internal fun transitionSpec(
+ from: SceneKey,
+ to: SceneKey,
+ key: TransitionKey?,
+ ): TransitionSpecImpl {
+ return cache
+ .getOrPut(from) { mutableMapOf() }
+ .getOrPut(to) { mutableMapOf() }
+ .getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- val spec = transition(from, to) { it.from == from && it.to == to }
+ private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
}
- val reversed = transition(from, to) { it.from == to && it.to == from }
+ val reversed = transition(from, to, key) { it.from == to && it.to == from }
if (reversed != null) {
return reversed.reversed()
}
val relaxedSpec =
- transition(from, to) {
+ transition(from, to, key) {
(it.from == from && it.to == null) || (it.to == to && it.from == null)
}
if (relaxedSpec != null) {
return relaxedSpec
}
- return transition(from, to) {
+ return transition(from, to, key) {
(it.from == to && it.to == null) || (it.to == from && it.from == null)
}
?.reversed()
@@ -77,11 +87,12 @@
private fun transition(
from: SceneKey,
to: SceneKey,
+ key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
var match: TransitionSpecImpl? = null
transitionSpecs.fastForEach { spec ->
- if (filter(spec)) {
+ if (spec.key == key && filter(spec)) {
if (match != null) {
error("Found multiple transition specs for transition $from => $to")
}
@@ -92,7 +103,7 @@
}
private fun defaultTransition(from: SceneKey, to: SceneKey) =
- TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
+ TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
companion object {
internal val DefaultSwipeSpec =
@@ -107,6 +118,9 @@
/** The definition of a transition between [from] and [to]. */
interface TransitionSpec {
+ /** The key of this [TransitionSpec]. */
+ val key: TransitionKey?
+
/**
* The scene we are transitioning from. If `null`, this spec can be used to animate from any
* scene.
@@ -164,12 +178,14 @@
}
internal class TransitionSpecImpl(
+ override val key: TransitionKey?,
override val from: SceneKey?,
override val to: SceneKey?,
private val transformationSpec: () -> TransformationSpecImpl,
) : TransitionSpec {
override fun reversed(): TransitionSpecImpl {
return TransitionSpecImpl(
+ key = key,
from = to,
to = from,
transformationSpec = {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 04e0937..d93911d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -42,10 +42,14 @@
* any scene. For the animation specification to apply only when transitioning between two
* specific scenes, use [from] instead.
*
+ * If [key] is not `null`, then this transition will only be used if the same key is specified
+ * when triggering the transition.
+ *
* @see from
*/
fun to(
to: SceneKey,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
@@ -55,7 +59,8 @@
* the destination scene via the [to] argument.
*
* When looking up which transition should be used when animating from scene A to scene B, we
- * pick the single transition matching one of these predicates (in order of importance):
+ * pick the single transition with the given [key] and matching one of these predicates (in
+ * order of importance):
* 1. from == A && to == B
* 2. to == A && from == B, which is then treated in reverse.
* 3. (from == A && to == null) || (from == null && to == B)
@@ -64,6 +69,7 @@
fun from(
from: SceneKey,
to: SceneKey? = null,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index df186a1..9b16d46 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -49,21 +49,27 @@
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
- override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
- return transition(from = null, to = to, builder)
+ override fun to(
+ to: SceneKey,
+ key: TransitionKey?,
+ builder: TransitionBuilder.() -> Unit
+ ): TransitionSpec {
+ return transition(from = null, to = to, key = key, builder)
}
override fun from(
from: SceneKey,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit
): TransitionSpec {
- return transition(from = from, to = to, builder)
+ return transition(from = from, to = to, key = key, builder)
}
private fun transition(
from: SceneKey?,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit,
): TransitionSpec {
fun transformationSpec(): TransformationSpecImpl {
@@ -75,7 +81,7 @@
)
}
- val spec = TransitionSpecImpl(from, to, ::transformationSpec)
+ val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
transitionSpecs.add(spec)
return spec
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 48825fb..c61917d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -45,7 +45,10 @@
@Test
fun isTransitioningTo_transition() {
val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+ transitionKey = null
+ )
assertThat(state.isTransitioning()).isTrue()
assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
@@ -91,6 +94,16 @@
}
@Test
+ fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+
+ // Progress is 0f, so we don't animate at all and directly snap back to A.
+ assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ }
+
+ @Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
@@ -106,4 +119,43 @@
testScheduler.advanceUntilIdle()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
}
+
+ @Test
+ fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions =
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ },
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ // Default transition from A to B.
+ assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Go back to A.
+ state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ testScheduler.advanceUntilIdle()
+ assertThat(state.currentTransition).isNull()
+ assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+ // Specific transition from A to B.
+ assertThat(
+ state.setTargetScene(
+ TestScenes.SceneB,
+ coroutineScope = this,
+ transitionKey = transitionkey,
+ )
+ )
+ .isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
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 d81631a..543ed04 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
@@ -384,7 +384,13 @@
scene(
TestScenes.SceneA,
userActions =
- mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+ mapOf(
+ Swipe.Down to
+ UserActionResult(
+ toScene = TestScenes.SceneB,
+ distance = verticalSwipeDistance,
+ )
+ ),
) {
Spacer(Modifier.fillMaxSize())
}
@@ -442,6 +448,23 @@
transition = layoutState.currentTransition
assertThat(transition).isNotNull()
assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
+
+ // Release the finger, animating back to scene A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(layoutState.currentTransition).isNull()
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+ // Swipe left by exactly touchSlop, so that the drag overSlop is 0f.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // We should still correctly compute that we are swiping down to scene B.
+ transition = layoutState.currentTransition
+ assertThat(transition).isNotNull()
+ assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
}
@Test
@@ -475,4 +498,54 @@
}
assertThat(layoutState.currentTransition).isNotNull()
}
+
+ @Test
+ fun transitionKey() {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ }
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(LayoutWidth, LayoutHeight)) {
+ scene(
+ TestScenes.SceneA,
+ userActions =
+ mapOf(
+ Swipe.Down to TestScenes.SceneB,
+ Swipe.Up to
+ UserActionResult(TestScenes.SceneB, transitionKey = transitionkey)
+ )
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down for the default transition from A to B.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+ }
+
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Move the pointer up to swipe to scene B using the new transition.
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 140baca..1beafcc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -177,7 +177,7 @@
// to B we defined.
val transformations =
transitions
- .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+ .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
.transformationSpec()
.transformations
@@ -207,7 +207,7 @@
// A => B does not have a custom spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB, key = null)
.transformationSpec()
.swipeSpec
)
@@ -216,7 +216,7 @@
// A => C has a custom swipe spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC, key = null)
.transformationSpec()
.swipeSpec
)
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 6d6d575..2d71a6e 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -89,7 +89,7 @@
SceneTransitionLayout(
currentScene,
onChangeScene,
- transitions { from(fromScene, to = toScene, transition) },
+ transitions { from(fromScene, to = toScene, builder = transition) },
layoutModifier,
) {
scene(fromScene, content = fromSceneContent)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index e050604..34c4dfb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -33,6 +33,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
@@ -518,6 +519,7 @@
awaitClose { context.contentResolver.unregisterContentObserver(observer) }
}
.onStart { emit(Unit) }
+ .flowOn(backgroundDispatcher)
}
private fun String.toIntent(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
index be6bb9c..107293e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
@@ -24,6 +24,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SysuiTestCaseSelfTest : SysuiTestCase() {
private val contextBeforeSetup = context
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 9287edf..c2f0c6f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index c86c747..b6605ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,6 +37,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 4853529..30eb782 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -37,6 +37,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorInversionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ce22e28..ed3b4c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index 39f0d57..0596205 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FaceHelpMessageDeferralTest : SysuiTestCase() {
val threshold = .75f
@Mock lateinit var logger: BiometricMessageDeferralLogger
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index a67b093..d317aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -37,6 +37,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index 4aea4f3..db83b3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -29,6 +29,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c193d14..fbb5415 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,18 +27,27 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,19 +61,22 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val selectedUserInteractor by lazy { kosmos.selectedUserInteractor }
+ private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
private val isInputEnabled = MutableStateFlow(true)
- private val underTest by lazy {
+ private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
+ isInputEnabled = isInputEnabled.asStateFlow(),
interactor = bouncerInteractor,
- isInputEnabled.asStateFlow(),
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
- }
@Before
fun setUp() {
@@ -270,6 +283,52 @@
assertThat(isTextFieldFocusRequested).isTrue()
}
+ @Test
+ fun isImeSwitcherButtonVisible() =
+ testScope.runTest {
+ val selectedUserId by collectLastValue(selectedUserInteractor.selectedUser)
+ selectUser(USER_INFOS.first())
+
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+
+ // Assert initial value, before the UI subscribes.
+ assertThat(underTest.isImeSwitcherButtonVisible.value).isFalse()
+
+ // Subscription starts; verify a fresh value is fetched.
+ val isImeSwitcherButtonVisible by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(isImeSwitcherButtonVisible).isTrue()
+
+ // Change the user, verify a fresh value is fetched.
+ selectUser(USER_INFOS.last())
+
+ assertThat(
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(
+ checkNotNull(selectedUserId)
+ )
+ )
+ .isFalse()
+ assertThat(isImeSwitcherButtonVisible).isFalse()
+
+ // Enable IMEs and add another subscriber; verify a fresh value is fetched.
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+ val collector2 by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(collector2).isTrue()
+ }
+
+ @Test
+ fun onImeSwitcherButtonClicked() =
+ testScope.runTest {
+ val displayId = 7
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isNotEqualTo(displayId)
+
+ underTest.onImeSwitcherButtonClicked(displayId)
+ runCurrent()
+
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
@@ -310,8 +369,45 @@
runCurrent()
}
+ private fun TestScope.selectUser(userInfo: UserInfo) {
+ kosmos.fakeUserRepository.selectedUser.value =
+ SelectedUserModel(
+ userInfo = userInfo,
+ selectionStatus = SelectionStatus.SELECTION_COMPLETE
+ )
+ advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
+ }
+
+ private suspend fun enableInputMethodsForUser(userId: Int) {
+ kosmos.fakeInputMethodRepository.setEnabledInputMethods(
+ userId,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ )
+ assertThat(inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(userId)).isTrue()
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
companion object {
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
+
+ private val USER_INFOS =
+ listOf(
+ UserInfo(100, "First user", 0),
+ UserInfo(101, "Second user", 0),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
index 55016bb..25a287c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -24,6 +24,7 @@
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PinInputViewModelTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 92b75cb..76b0d4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -40,6 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var mediaData: MediaData
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 820bfbf..d15e15e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: CommunalPrefsRepositoryImpl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index c4a8582..0c66bbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -38,6 +38,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index ee01bf9..c5485c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -642,6 +642,53 @@
}
@Test
+ fun isCommunalVisible() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+ )
+ communalRepository.setTransitionState(transitionState)
+
+ // isCommunalVisible is false when not on communal.
+ val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
+ assertThat(isCommunalVisible).isEqualTo(false)
+
+ // Start transition to communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Blank,
+ toScene = CommunalSceneKey.Communal,
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is true once transition starts.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value =
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+
+ // isCommunalVisible is true since we're on communal.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Communal,
+ toScene = CommunalSceneKey.Blank,
+ progress = flowOf(1.0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is still true as the false as soon as transition away runs.
+ assertThat(isCommunalVisible).isEqualTo(true)
+ }
+
+ @Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
underTest.showWidgetEditor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 3aa99c4..89a4c04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -23,16 +23,27 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var testableLooper: TestableLooper
private lateinit var underTest: CommunalAppWidgetHost
@@ -43,9 +54,11 @@
underTest =
CommunalAppWidgetHost(
context = context,
+ backgroundScope = kosmos.applicationCoroutineScope,
hostId = 116,
interactionHandler = mock(),
- looper = testableLooper.looper
+ looper = testableLooper.looper,
+ logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
)
}
@@ -64,4 +77,23 @@
assertThat(view).isNotNull()
assertThat(view.appWidgetId).isEqualTo(appWidgetId)
}
+
+ @Test
+ fun appWidgetIdToRemove_emit() =
+ testScope.runTest {
+ val appWidgetIdToRemove by collectLastValue(underTest.appWidgetIdToRemove)
+
+ // Nothing should be emitted yet
+ assertThat(appWidgetIdToRemove).isNull()
+
+ underTest.onAppWidgetRemoved(appWidgetId = 1)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(1)
+
+ underTest.onAppWidgetRemoved(appWidgetId = 2)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(2)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index a3654b6..032d76f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -21,14 +21,21 @@
import androidx.test.filters.SmallTest
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.CommunalWidgetContentModel
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,6 +54,8 @@
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+ private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
+
private lateinit var underTest: CommunalAppWidgetHostStartable
@Before
@@ -54,6 +63,9 @@
MockitoAnnotations.initMocks(this)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ appWidgetIdToRemove = MutableSharedFlow()
+ whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
+
underTest =
CommunalAppWidgetHostStartable(
appWidgetHost,
@@ -120,6 +132,38 @@
}
}
+ @Test
+ fun removeAppWidgetReportedByHost() =
+ with(kosmos) {
+ testScope.runTest {
+ // Set up communal widgets
+ val widget1 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) }
+ val widget2 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) }
+ val widget3 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) }
+ fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
+
+ underTest.start()
+
+ // Assert communal widgets has 3
+ val communalWidgets by
+ collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
+ assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+
+ // Report app widget 1 to remove and assert widget removed
+ appWidgetIdToRemove.emit(1)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2, widget3)
+
+ // Report app widget 3 to remove and assert widget removed
+ appWidgetIdToRemove.emit(3)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2)
+ }
+ }
+
private suspend fun setCommunalAvailable(available: Boolean) =
with(kosmos) {
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index b54c5bd..9536084 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -32,6 +32,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 32943a1..51db451 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -33,6 +33,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceUnlockedInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/binder/LiftToRunFaceAuthBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/binder/LiftToRunFaceAuthBinderTest.kt
new file mode 100644
index 0000000..e9e85c9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/binder/LiftToRunFaceAuthBinderTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.ui.binder
+
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.hardware.Sensor
+import android.hardware.TriggerEventListener
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.deviceentry.ui.binder.liftToRunFaceAuthBinder
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.sensors.asyncSensorManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class LiftToRunFaceAuthBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sensorManager = kosmos.asyncSensorManager
+ private val powerRepository = kosmos.fakePowerRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val bouncerRepository = kosmos.keyguardBouncerRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val packageManager = kosmos.packageManager
+
+ @Captor private lateinit var triggerEventListenerCaptor: ArgumentCaptor<TriggerEventListener>
+ @Mock private lateinit var mockSensor: Sensor
+
+ private val underTest = kosmos.liftToRunFaceAuthBinder
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
+ whenever(sensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)).thenReturn(mockSensor)
+ }
+
+ @Test
+ fun doNotListenForGesture() =
+ testScope.runTest {
+ start()
+ verifyNeverRequestsTriggerSensor()
+ }
+
+ @Test
+ fun awakeKeyguard_listenForGesture() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(true)
+ runCurrent()
+ verifyRequestTriggerSensor()
+ }
+
+ @Test
+ fun faceNotEnrolled_listenForGesture() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(true)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ runCurrent()
+ verifyNeverRequestsTriggerSensor()
+ }
+
+ @Test
+ fun notInteractive_doNotListenForGesture() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(true)
+ powerRepository.setInteractive(false)
+ runCurrent()
+ verifyNeverRequestsTriggerSensor()
+ }
+
+ @Test
+ fun primaryBouncer_listenForGesture() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(false)
+ givenPrimaryBouncerShowing()
+ runCurrent()
+ verifyRequestTriggerSensor()
+ }
+
+ @Test
+ fun alternateBouncer_listenForGesture() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(false)
+ givenAlternateBouncerShowing()
+ runCurrent()
+ verifyRequestTriggerSensor()
+ }
+
+ @Test
+ fun restartListeningForGestureAfterSensorTrigger() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(true)
+ runCurrent()
+ verifyRequestTriggerSensor()
+ clearInvocations(sensorManager)
+
+ triggerEventListenerCaptor.value.onTrigger(null)
+ runCurrent()
+ verifyRequestTriggerSensor()
+ }
+
+ @Test
+ fun cancelTriggerSensor_keyguardNotAwakeAnymore() =
+ testScope.runTest {
+ start()
+ givenAwakeKeyguard(true)
+ runCurrent()
+ verifyRequestTriggerSensor()
+
+ givenAwakeKeyguard(false)
+ runCurrent()
+ verifyCancelTriggerSensor()
+ }
+
+ private fun start() {
+ underTest.start()
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ givenAwakeKeyguard(false)
+ givenBouncerNotShowing()
+ }
+
+ private fun givenAwakeKeyguard(isAwake: Boolean) {
+ powerRepository.setInteractive(isAwake)
+ keyguardRepository.setKeyguardShowing(isAwake)
+ keyguardRepository.setKeyguardOccluded(false)
+ }
+
+ private fun givenPrimaryBouncerShowing() {
+ bouncerRepository.setPrimaryShow(true)
+ bouncerRepository.setAlternateVisible(false)
+ }
+
+ private fun givenBouncerNotShowing() {
+ bouncerRepository.setPrimaryShow(false)
+ bouncerRepository.setAlternateVisible(false)
+ }
+
+ private fun givenAlternateBouncerShowing() {
+ bouncerRepository.setPrimaryShow(false)
+ bouncerRepository.setAlternateVisible(true)
+ }
+
+ private fun verifyRequestTriggerSensor() {
+ verify(sensorManager).requestTriggerSensor(capture(triggerEventListenerCaptor), any())
+ }
+
+ private fun verifyNeverRequestsTriggerSensor() {
+ verify(sensorManager, never()).requestTriggerSensor(any(), any())
+ }
+
+ private fun verifyCancelTriggerSensor() {
+ verify(sensorManager).cancelTriggerSensor(any(), any())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index 2c6c793..d9dcfdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DreamOverlayCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamOverlayCallbackController.Callback
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index d379dc6..5ae8595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
@Mock
NotificationListener mNotificationListener;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 8bf878c..b46f2aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -50,6 +50,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
index 7ff345c..ad353ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -37,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarItemsProvider.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index e0c6ab2..cb5702ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -42,6 +42,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class AssistantAttentionConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 480754c..96d3c93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
new file mode 100644
index 0000000..efccf7a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.homeControlsComponentInteractor by
+ Kosmos.Fixture {
+ HomeControlsComponentInteractor(
+ selectedComponentRepository = selectedComponentRepository,
+ controlsComponent,
+ authorizedPanelsRepository = authorizedPanelsRepository,
+ userRepository = fakeUserRepository,
+ bgScope = applicationCoroutineScope,
+ )
+ }
+
+val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
+val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
+val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
new file mode 100644
index 0000000..ce74a90
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsComponentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+ private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+ private lateinit var underTest: HomeControlsComponentInteractor
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
+ controlsComponent = kosmos.controlsComponent
+ authorizedPanelsRepository = kosmos.authorizedPanelsRepository
+ controlsListingController = kosmos.controlsListingController
+ selectedComponentRepository = kosmos.selectedComponentRepository
+
+ selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ underTest =
+ HomeControlsComponentInteractor(
+ selectedComponentRepository,
+ controlsComponent,
+ authorizedPanelsRepository,
+ userRepository,
+ kosmos.applicationCoroutineScope,
+ )
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(
+ listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ )
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate(false)
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameForDifferentUsers() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.empty())
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
+ val listings =
+ listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+ val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
+ callback.onServicesUpdated(listings)
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+
+ private const val ANOTHER_USER_ID = 1
+ private val ANOTHER_USER =
+ UserInfo(
+ /* id= */ ANOTHER_USER_ID,
+ /* name= */ "another user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ private const val TEST_PACKAGE = "pkg"
+ private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ private val TEST_SELECTED_COMPONENT_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ true
+ )
+ private val TEST_SELECTED_COMPONENT_NON_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
new file mode 100644
index 0000000..d28b6bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import android.app.Activity
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsDreamServiceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+ @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
+ @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
+ @Mock private lateinit var activity: Activity
+ private val logBuffer: LogBuffer = create()
+
+ private lateinit var underTest: HomeControlsDreamService
+ private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
+ private lateinit var fakeDreamActivityProvider: DreamActivityProvider
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
+ .thenReturn(taskFragmentComponent)
+
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ controlsComponent = kosmos.controlsComponent
+ controlsListingController = kosmos.controlsListingController
+
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
+
+ fakeDreamActivityProvider = DreamActivityProvider { activity }
+ underTest =
+ HomeControlsDreamService(
+ controlsSettingsRepository,
+ taskFragmentComponentFactory,
+ homeControlsComponentInteractor,
+ fakeDreamActivityProvider,
+ logBuffer
+ )
+ }
+
+ @Test
+ fun testOnAttachedToWindowCreatesTaskFragmentComponent() {
+ underTest.onAttachedToWindow()
+ verify(taskFragmentComponentFactory).create(any(), any(), any(), any())
+ }
+
+ @Test
+ fun testOnDetachedFromWindowDestroyTaskFragmentComponent() {
+ underTest.onAttachedToWindow()
+ underTest.onDetachedFromWindow()
+ verify(taskFragmentComponent).destroy()
+ }
+
+ @Test
+ fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() {
+ fakeDreamActivityProvider = DreamActivityProvider { null }
+ underTest =
+ HomeControlsDreamService(
+ controlsSettingsRepository,
+ taskFragmentComponentFactory,
+ homeControlsComponentInteractor,
+ fakeDreamActivityProvider,
+ logBuffer
+ )
+
+ underTest.onAttachedToWindow()
+ verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
+ }
+
+ companion object {
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
new file mode 100644
index 0000000..6610e70
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import java.util.Optional
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsDreamStartableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
+ private lateinit var selectedComponentRepository: SelectedComponentRepository
+ private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+
+ private lateinit var startable: HomeControlsDreamStartable
+ private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ selectedComponentRepository = kosmos.selectedComponentRepository
+ authorizedPanelsRepository = kosmos.authorizedPanelsRepository
+ userRepository = kosmos.fakeUserRepository
+ controlsComponent = kosmos.controlsComponent
+ controlsListingController = kosmos.controlsListingController
+
+ userRepository.setUserInfos(listOf(PRIMARY_USER))
+
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)))
+
+ homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
+
+ startable =
+ HomeControlsDreamStartable(
+ mContext,
+ packageManager,
+ homeControlsComponentInteractor,
+ kosmos.applicationCoroutineScope
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartEnablesHomeControlsDreamServiceWhenPanelComponentIsNotNull() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartDisablesHomeControlsDreamServiceWhenPanelComponentIsNull() =
+ testScope.runTest {
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
+ }
+
+ @Test
+ @DisableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ testScope.runTest {
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ private val TEST_SELECTED_COMPONENT_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ true
+ )
+ private val TEST_SELECTED_COMPONENT_NON_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 74c1970..2a9bc4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -28,7 +28,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
@@ -47,8 +46,6 @@
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock
DreamTouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@@ -59,17 +56,10 @@
MockitoAnnotations.initMocks(this);
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
- mNotificationShadeWindowController,
INITIATION_WIDTH);
}
@Test
- public void testSessionStartForcesShadeOpen() {
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
- }
-
- @Test
public void testEventPropagation() {
final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 017fdbe..97052a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class BouncerlessScrimControllerTest extends SysuiTestCase {
@Mock
BouncerlessScrimController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
index 4ee4a60..ebbcf98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class ScrimManagerTest extends SysuiTestCase {
@Mock
ScrimController mBouncerlessScrimController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
index 61b2057..db52a45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
@@ -28,6 +28,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FoldPostureTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
new file mode 100644
index 0000000..857cdce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var inputMethodManager: InputMethodManager
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InputMethodRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf())
+
+ underTest =
+ InputMethodRepositoryImpl(
+ backgroundDispatcher = kosmos.testDispatcher,
+ inputMethodManager = inputMethodManager,
+ )
+ }
+
+ @Test
+ fun enabledInputMethods_noImes_emptyFlow() =
+ testScope.runTest {
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+
+ assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun selectedInputMethodSubtypes_returnsSubtypeList() =
+ testScope.runTest {
+ val subtypeId = 123
+ val isAuxiliary = true
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf(mock<InputMethodInfo>()))
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(
+ listOf(
+ InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(subtypeId)
+ .setIsAuxiliary(isAuxiliary)
+ .build()
+ )
+ )
+
+ val result = underTest.selectedInputMethodSubtypes()
+ assertThat(result).hasSize(1)
+ assertThat(result.first().subtypeId).isEqualTo(subtypeId)
+ assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
+ }
+
+ @Test
+ fun showImePicker_forwardsDisplayId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+
+ verify(inputMethodManager)
+ .showInputMethodPickerFromSystem(
+ /* showAuxiliarySubtypes = */ eq(true),
+ /* displayId = */ eq(displayId)
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ private val USER_HANDLE = UserHandle.of(USER_ID)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
new file mode 100644
index 0000000..d23ff2a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val fakeInputMethodRepository = kosmos.fakeInputMethodRepository
+
+ private val underTest = InputMethodInteractor(repository = kosmos.inputMethodRepository)
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noImes_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(USER_ID)
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noMatches_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 1, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneMatch_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoMatches_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneWithNonAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 2),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoWithAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 5, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasOneSubtype_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(InputMethodModel.Subtype(1, isAuxiliary = false))
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasTwoSubtypes_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(
+ InputMethodModel.Subtype(subtypeId = 1, isAuxiliary = false),
+ InputMethodModel.Subtype(subtypeId = 2, isAuxiliary = false),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun showImePicker_shownOnCorrectId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+
+ assertThat(fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index e20d3af..ee3e241 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -43,6 +43,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 4ae144c..77e0f4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -42,6 +42,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
@Mock private lateinit var context: Context
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 521dea3..ca64cec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -40,6 +40,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var controller: QRCodeScannerController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 6b7d263..558e7e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -50,6 +50,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
index ae6c5b7..a0b8542 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -40,6 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DevicePostureRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DevicePostureRepository
private lateinit var testScope: TestScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c01c79d..128b465 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index ee47c58f4..5f0f24d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -46,6 +46,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class TrustRepositoryTest : SysuiTestCase() {
@Mock private lateinit var trustManager: TrustManager
@Captor private lateinit var listener: ArgumentCaptor<TrustManager.TrustListener>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6828041..9368097 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
index e850456..4695ea4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -13,6 +13,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index d4dd2ac..0b80ff8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -36,6 +36,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenContentViewModelTest : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 30ac344..6fc5be1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -19,7 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -52,11 +52,9 @@
val testScope = kosmos.testScope
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- val primaryBouncerInteractor = kosmos.primaryBouncerInteractor
+ val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
- val underTest by lazy {
- kosmos.primaryBouncerToGoneTransitionViewModel
- }
+ val underTest by lazy { kosmos.primaryBouncerToGoneTransitionViewModel }
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
new file mode 100644
index 0000000..b7b3fdb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+ private val mapper by lazy {
+ FontScalingTileMapper(
+ context.orCreateTestableResources
+ .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun activeStateMatchesEnabledModel() {
+ val inputModel = FontScalingTileModel
+
+ val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+ val expectedState = createFontScalingTileState()
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createFontScalingTileState(): QSTileState =
+ QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ )!!,
+ null
+ )
+ },
+ context.getString(R.string.quick_settings_font_scaling_label),
+ QSTileState.ActivationState.ACTIVE,
+ null,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ context.getString(R.string.quick_settings_font_scaling_label),
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 0000000..39bc8a6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+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.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+ private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+ private val testUser = UserHandle.of(1)
+
+ @Test
+ fun collectsExactlyOneValue() = runTest {
+ val flowValues by
+ collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValues.size).isEqualTo(1)
+ }
+
+ @Test
+ fun lastValueIsNotEmpty() = runTest {
+ val flowValue by
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValue).isNotNull()
+ }
+
+ @Test
+ fun isAvailable() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ Truth.assertThat(availability).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 0000000..2384915
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.provider.Settings
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+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.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardStateController = FakeKeyguardStateController()
+
+ private lateinit var underTest: FontScalingTileUserActionInteractor
+
+ @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+ @Before
+ fun setup() {
+ activityStarter = mock<ActivityStarter>()
+ dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+ dialog = mock<SystemUIDialog>()
+ fontScalingDialogDelegate =
+ mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+ argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+ underTest =
+ FontScalingTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ qsTileIntentUserActionHandler,
+ { fontScalingDialogDelegate },
+ keyguardStateController,
+ dialogLaunchAnimator,
+ activityStarter
+ )
+ }
+
+ @Test
+ fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = false
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+ }
+
+ @Test
+ fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = true
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator, never())
+ .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(dialog).show()
+ }
+
+ @Test
+ fun handleLongClick() =
+ kosmos.testScope.runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 504ded3..d6d2509 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,8 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -262,6 +264,7 @@
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
startable.start()
@@ -518,6 +521,17 @@
assertCurrentScene(SceneKey.Lockscreen)
}
+ @Test
+ fun factoryResetProtectionActive_isNotVisible() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
+ assertThat(isVisible).isTrue()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isActive = true)
+
+ assertThat(isVisible).isFalse()
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index b267720..2ad872c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -40,6 +40,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SceneContainerRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
index 7ae501d..13b5b54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16cb623..1abbc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -112,6 +113,7 @@
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
index cb83e7c..bbbee90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BcSmartspaceConfigProviderTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlags
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
index 0b5aea7..089eb43e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
@@ -37,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenPreconditionTest : SysuiTestCase() {
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
index bf33010..6616786 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
@@ -52,6 +52,7 @@
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenTargetFilterTest : SysuiTestCase() {
@Mock private lateinit var secureSettings: SecureSettings
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
index 8a0400d..f7a8858 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -38,6 +38,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
index 12469dd..60da53c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 6a2e317..4d7d5d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -87,21 +87,21 @@
}
@Test
- fun updateShadeExpansion() =
+ fun shadeExpansion_goneToShade() =
testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Gone)
+ )
+ sceneInteractor.setTransitionState(transitionState)
val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
assertThat(expandFraction).isEqualTo(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
- )
- sceneInteractor.setTransitionState(transitionState)
sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
val transitionProgress = MutableStateFlow(0f)
transitionState.value =
ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
+ fromScene = SceneKey.Gone,
toScene = SceneKey.Shade,
progress = transitionProgress,
isInitiatedByUserInput = false,
@@ -118,4 +118,49 @@
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
assertThat(expandFraction).isWithin(0.01f).of(1f)
}
+
+ @Test
+ fun shadeExpansion_idleOnLockscreen() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ @Test
+ fun shadeExpansion_shadeToQs() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Shade)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
+ val transitionProgress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.QuickSettings,
+ progress = transitionProgress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ val steps = 10
+ repeat(steps) { repetition ->
+ val progress = (1f / steps) * (repetition + 1)
+ transitionProgress.value = progress
+ runCurrent()
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
+ assertThat(expandFraction).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index c7411cd..ffe6e6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
private val kosmos = Kosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f..1f6ba29 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@
mOccluded = occluded;
}
+ public void setShowing(boolean isShowing) {
+ mShowing = isShowing;
+ }
+
@Override
public boolean isShowing() {
return mShowing;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index ce00250..18825a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -28,6 +28,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DisabledWifiRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DisabledWifiRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 7fbbfc7..84c728c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -43,6 +43,7 @@
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
index ebc81be..4c8bbe0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
index 26c0f80..7ec0a61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
index b4a0a37..96d1c0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BooleanFlowOperatorsTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 5d85fba..c9e2989 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -264,15 +264,18 @@
}
}
- @ProvidesInterface(version = BooleanState.VERSION)
- public static class BooleanState extends State {
+ /**
+ * Distinguished from [BooleanState] for use-case purposes such as allowing null secondary label
+ */
+ @ProvidesInterface(version = AdapterState.VERSION)
+ class AdapterState extends State {
public static final int VERSION = 1;
public boolean value;
public boolean forceExpandIcon;
@Override
public boolean copyTo(State other) {
- final BooleanState o = (BooleanState) other;
+ final AdapterState o = (AdapterState) other;
final boolean changed = super.copyTo(other)
|| o.value != value
|| o.forceExpandIcon != forceExpandIcon;
@@ -291,6 +294,18 @@
@Override
public State copy() {
+ AdapterState state = new AdapterState();
+ copyTo(state);
+ return state;
+ }
+ }
+
+ @ProvidesInterface(version = BooleanState.VERSION)
+ class BooleanState extends AdapterState {
+ public static final int VERSION = 1;
+
+ @Override
+ public State copy() {
BooleanState state = new BooleanState();
copyTo(state);
return state;
diff --git a/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png b/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png
new file mode 100644
index 0000000..00b461b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change_inset.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change_inset.xml
new file mode 100644
index 0000000..02486bf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change_inset.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/accessibility_window_magnification_drag_handle_background_change"
+ android:insetBottom="@dimen/magnification_inner_border_margin"
+ android:insetLeft="@dimen/magnification_inner_border_margin"
+ android:insetRight="@dimen/magnification_inner_border_margin"
+ android:insetTop="@dimen/magnification_inner_border_margin" />
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_inset.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_inset.xml
new file mode 100644
index 0000000..bfb7c47
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_inset.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/accessibility_window_magnification_drag_handle_background"
+ android:insetBottom="@dimen/magnification_inner_border_margin"
+ android:insetLeft="@dimen/magnification_inner_border_margin"
+ android:insetRight="@dimen/magnification_inner_border_margin"
+ android:insetTop="@dimen/magnification_inner_border_margin" />
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index a8a048d..6286f34 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -117,12 +117,11 @@
android:id="@+id/drag_handle"
android:layout_width="@dimen/magnification_drag_view_size"
android:layout_height="@dimen/magnification_drag_view_size"
- android:layout_margin="@dimen/magnification_inner_border_margin"
android:layout_gravity="right|bottom"
android:padding="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:src="@drawable/ic_move_magnification"
- android:background="@drawable/accessibility_window_magnification_drag_handle_background"/>
+ android:background="@drawable/accessibility_window_magnification_drag_handle_background_inset"/>
<ImageView
android:id="@+id/close_button"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 17719d1..7a83070 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -991,7 +991,7 @@
<!-- Component name for Home Panel Dream -->
<string name="config_homePanelDreamComponent" translatable="false">
- @null
+ com.android.systemui/com.android.systemui.dreams.homecontrols.HomeControlsDreamService
</string>
<!--
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4209c1f..33bdca3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -903,6 +903,10 @@
Keep it the same as in Launcher-->
<dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+ <!-- Width and height used to filter widgets displayed in the communal widget picker -->
+ <dimen name="communal_widget_picker_desired_width">464dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">307dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
@@ -1249,7 +1253,7 @@
<dimen name="magnification_drag_corner_margin">8dp</dimen>
<dimen name="magnification_frame_move_short">5dp</dimen>
<dimen name="magnification_frame_move_long">25dp</dimen>
- <dimen name="magnification_drag_view_size">36dp</dimen>
+ <dimen name="magnification_drag_view_size">70dp</dimen>
<dimen name="magnification_controls_size">90dp</dimen>
<dimen name="magnification_switch_button_size">56dp</dimen>
<dimen name="magnification_switch_button_padding">6dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7943588..8971859 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3314,4 +3314,8 @@
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
<!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_value">Level %1$d of %2$d</string>
+ <!-- Label for home control panel [CHAR LIMIT=30] -->
+ <string name="home_controls_dream_label">Home Controls</string>
+ <!-- Description for home control panel [CHAR LIMIT=50] -->
+ <string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
</resources>
diff --git a/packages/SystemUI/res/xml/home_controls_dream_metadata.xml b/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
new file mode 100644
index 0000000..eb7c79e
--- /dev/null
+++ b/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:showClockAndComplications="false"
+ android:previewImage="@drawable/homecontrols_sq"
+ />
\ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 326c7ef..4e04af6 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -53,6 +53,7 @@
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
+ "WindowManager-Shell-shared",
"tracinglib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 88b9c02..a78080f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -38,7 +38,7 @@
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
-import com.android.wm.shell.util.CounterRotator;
+import com.android.wm.shell.shared.CounterRotator;
public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
private static final String TAG = "RemoteAnimRunnerCompat";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index d4ac195..e4d9243 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -22,7 +22,7 @@
import android.window.TransitionInfo;
import android.window.TransitionInfo.Change;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Predicate;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 82410fd..91e6b62 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -39,7 +39,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -47,7 +47,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockMessageBuffers
@@ -92,7 +91,7 @@
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val zenModeController: ZenModeController,
) {
var loggers = listOf(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ad30317..28013c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -196,6 +196,9 @@
mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
+ } else {
+ removeView(findViewById(R.id.lockscreen_clock_view));
+ removeView(findViewById(R.id.lockscreen_clock_view_large));
}
onConfigChanged();
}
@@ -263,6 +266,9 @@
}
void updateClockTargetRegions() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
if (mClock != null) {
if (mSmallClockFrame.isLaidOut()) {
Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index de7c12d..2db3795 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -246,8 +246,11 @@
protected void onInit() {
mKeyguardSliceViewController.init();
- mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
- mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ if (!migrateClocksToBlueprint()) {
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ }
+
if (!mOnlyClock) {
mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous
@@ -526,13 +529,15 @@
*/
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
+ if (!migrateClocksToBlueprint()) {
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
+ x, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
+ scale, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
+ scale, props, animate);
- PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
- x, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
- scale, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
- scale, props, animate);
+ }
if (mStatusArea != null) {
PropertyAnimator.setProperty(mStatusArea, KeyguardStatusAreaView.TRANSLATE_X_AOD,
@@ -550,6 +555,10 @@
return 0;
}
+ if (migrateClocksToBlueprint()) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
// This gets the expected clock bottom if mLargeClockFrame had a top margin, but it's
// top margin only contributed to height and didn't move the top of the view (as this
@@ -581,6 +590,9 @@
}
boolean isClockTopAligned() {
+ if (migrateClocksToBlueprint()) {
+ return mKeyguardClockInteractor.getClockSize().getValue() == LARGE;
+ }
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index bddf3b0..d2ad096 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -97,6 +97,26 @@
)
}
+ fun logUpdateLockScreenUserLockedMsg(
+ userId: Int,
+ userUnlocked: Boolean,
+ encryptedOrLockdown: Boolean,
+ ) {
+ buffer.log(
+ KeyguardIndicationController.TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = userId
+ bool1 = userUnlocked
+ bool2 = encryptedOrLockdown
+ },
+ {
+ "updateLockScreenUserLockedMsg userId=$int1 " +
+ "userUnlocked:$bool1 encryptedOrLockdown:$bool2"
+ }
+ )
+ }
+
fun logUpdateBatteryIndication(
powerIndication: String,
pluggedIn: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 33728ac..d65cd5c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1698,8 +1698,8 @@
mSettingsPanelVisibility = settingsPanelIsShown;
mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
- ? R.drawable.accessibility_window_magnification_drag_handle_background_change
- : R.drawable.accessibility_window_magnification_drag_handle_background));
+ ? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset
+ : R.drawable.accessibility_window_magnification_drag_handle_background_inset));
PorterDuffColorFilter filter = new PorterDuffColorFilter(
mContext.getColor(settingsPanelIsShown
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35..4047623 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@
companion object {
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
+ const val FONT_SCALING_TILE_SPEC = "font_scaling"
@Provides
@IntoMap
@@ -155,5 +160,36 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_font_scaling,
+ labelRes = R.string.quick_settings_font_scaling_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileViewModel(
+ factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+ mapper: FontScalingTileMapper,
+ stateInteractor: FontScalingTileDataInteractor,
+ userActionInteractor: FontScalingTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(FONT_SCALING_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 49f34f1..454ed27 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -39,7 +39,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.onSubscriberAdded
import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
@@ -54,7 +54,6 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -355,10 +354,7 @@
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
// Emits a value only when the number of downstream subscribers of this flow
// increases.
- flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
- ->
- current > previous
- },
+ flow.onSubscriberAdded(),
) { selectedUserId, _ ->
selectedUserId
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 70be0f2..a742c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -19,22 +19,15 @@
import android.hardware.biometrics.AuthenticateOptions
import android.hardware.biometrics.IBiometricContextListener
import android.util.Log
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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.keyguard.WakefulnessLifecycle
+import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FoldStateProvider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
@@ -80,15 +73,11 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val foldProvider: FoldStateProvider,
+ deviceStateRepository: DeviceStateRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : LogContextInteractor {
- init {
- applicationScope.launch { foldProvider.start() }
- }
-
override val displayState =
keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
when (it.to) {
@@ -123,32 +112,21 @@
.distinctUntilChanged()
override val foldState: Flow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : FoldStateProvider.FoldUpdatesListener {
- override fun onHingeAngleUpdate(angle: Float) {}
-
- override fun onFoldUpdate(@FoldStateProvider.FoldUpdate update: Int) {
- val loggedState =
- when (update) {
- FOLD_UPDATE_FINISH_HALF_OPEN ->
- IBiometricContextListener.FoldState.HALF_OPENED
- FOLD_UPDATE_FINISH_FULL_OPEN ->
- IBiometricContextListener.FoldState.FULLY_OPENED
- FOLD_UPDATE_FINISH_CLOSED ->
- IBiometricContextListener.FoldState.FULLY_CLOSED
- else -> null
- }
- if (loggedState != null) {
- trySendWithFailureLogging(loggedState, TAG)
- }
- }
- }
-
- foldProvider.addCallback(callback)
- trySendWithFailureLogging(IBiometricContextListener.FoldState.UNKNOWN, TAG)
- awaitClose { foldProvider.removeCallback(callback) }
+ deviceStateRepository.state
+ .map {
+ when (it) {
+ DeviceStateRepository.DeviceState.UNFOLDED,
+ DeviceStateRepository.DeviceState.REAR_DISPLAY,
+ DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY ->
+ IBiometricContextListener.FoldState.FULLY_OPENED
+ DeviceStateRepository.DeviceState.FOLDED ->
+ IBiometricContextListener.FoldState.FULLY_CLOSED
+ DeviceStateRepository.DeviceState.HALF_FOLDED ->
+ IBiometricContextListener.FoldState.HALF_OPENED
+ else -> IBiometricContextListener.FoldState.UNKNOWN
+ }
}
+ .distinctUntilChanged()
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
@@ -178,6 +156,3 @@
private const val TAG = "ContextRepositoryImpl"
}
}
-
-private val WakefulnessLifecycle.isAwake: Boolean
- get() = wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index 7265c0c..d849b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -21,8 +21,6 @@
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
/** Provides access to bouncer-related application state. */
@SysUISingleton
@@ -31,15 +29,10 @@
constructor(
private val flags: FeatureFlagsClassic,
) {
- private val _message = MutableStateFlow<String?>(null)
/** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> = _message.asStateFlow()
+ val message = MutableStateFlow<String?>(null)
/** Whether the user switcher should be displayed within the bouncer UI on large screens. */
val isUserSwitcherVisible: Boolean
get() = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
-
- fun setMessage(message: String?) {
- _message.value = message
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index c8ce245..d8be1af 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -120,7 +120,7 @@
}
fun setMessage(message: String?) {
- repository.setMessage(message)
+ repository.message.value = message
}
/**
@@ -129,13 +129,13 @@
*/
fun resetMessage() {
applicationScope.launch {
- repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
}
}
/** Removes the user-facing message. */
fun clearMessage() {
- repository.setMessage(null)
+ setMessage(null)
}
/**
@@ -196,7 +196,7 @@
* message without having the attempt trigger lockout.
*/
private suspend fun showWrongInputMessage() {
- repository.setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
}
/** Notifies that the input method editor (software keyboard) has been hidden by the user. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4d686a1..4466cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -34,7 +34,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -66,8 +68,10 @@
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
private val authenticationInteractor: AuthenticationInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
@@ -346,8 +350,10 @@
is AuthenticationMethodModel.Password ->
PasswordBouncerViewModel(
viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
+ interactor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
is AuthenticationMethodModel.Pattern ->
PatternBouncerViewModel(
@@ -467,11 +473,13 @@
@Application applicationScope: CoroutineScope,
@Main mainDispatcher: CoroutineDispatcher,
bouncerInteractor: BouncerInteractor,
+ imeInteractor: InputMethodInteractor,
simBouncerInteractor: SimBouncerInteractor,
+ actionButtonInteractor: BouncerActionButtonInteractor,
authenticationInteractor: AuthenticationInteractor,
+ selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
- actionButtonInteractor: BouncerActionButtonInteractor,
clock: SystemClock,
devicePolicyManager: DevicePolicyManager,
): BouncerViewModel {
@@ -480,8 +488,10 @@
applicationScope = applicationScope,
mainDispatcher = mainDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = imeInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 5c9c997..1c8b84d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,23 +16,32 @@
package com.android.systemui.bouncer.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.onSubscriberAdded
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
viewModelScope: CoroutineScope,
- interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
+ interactor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
) :
AuthMethodBouncerViewModel(
viewModelScope = viewModelScope,
@@ -49,6 +58,9 @@
override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+ /** Informs the UI whether the input method switcher button should be visible. */
+ val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+
/** Whether the text field element currently has focus. */
private val isTextFieldFocused = MutableStateFlow(false)
@@ -87,6 +99,13 @@
_password.value = newPassword
}
+ /** Notifies that the user clicked the button to change the input method. */
+ fun onImeSwitcherButtonClicked(displayId: Int) {
+ viewModelScope.launch {
+ inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+ }
+ }
+
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
fun onAuthenticateKeyPressed() {
if (_password.value.isNotEmpty()) {
@@ -103,4 +122,35 @@
fun onTextFieldFocusChanged(isFocused: Boolean) {
isTextFieldFocused.value = isFocused
}
+
+ /**
+ * Whether the input method switcher button should be displayed in the password bouncer UI. The
+ * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
+ * shortly updated with a fresh value.
+ *
+ * Note: Each added subscription triggers an IPC call in the background, so this should only be
+ * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
+ */
+ private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
+ val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
+ viewModelScope.launch {
+ // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
+ // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+ combine(
+ // InputMethodManagerService sometimes takes some time to update its internal
+ // state when the selected user changes. As a workaround, delay fetching the IME
+ // info.
+ selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+ isImeSwitcherButtonVisible.onSubscriberAdded()
+ ) { selectedUserId, _ ->
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+ }
+ .collect { isImeSwitcherButtonVisible.value = it }
+ }
+ return isImeSwitcherButtonVisible.asStateFlow()
+ }
+
+ companion object {
+ @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index ab0a2d0d..d7f163b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.CommunalLog
@@ -35,6 +36,7 @@
import dagger.Provides
import java.util.Optional
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface CommunalWidgetRepositoryModule {
@@ -52,10 +54,19 @@
@Provides
fun provideCommunalAppWidgetHost(
@Application context: Context,
+ @Background backgroundScope: CoroutineScope,
interactionHandler: WidgetInteractionHandler,
@Main looper: Looper,
+ @CommunalLog logBuffer: LogBuffer,
): CommunalAppWidgetHost {
- return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID, interactionHandler, looper)
+ return CommunalAppWidgetHost(
+ context,
+ backgroundScope,
+ APP_WIDGET_HOST_ID,
+ interactionHandler,
+ looper,
+ logBuffer,
+ )
}
@SysUISingleton
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 80fee64..192c9001 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
@@ -129,9 +129,18 @@
.distinctUntilChanged()
/**
- * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
- * [CommunalSceneKey.Communal].
+ * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
+ * the [CommunalSceneKey.Communal].
+ *
+ * This will be true as soon as the desired scene is set programmatically or at whatever point
+ * during a fling that SceneTransitionLayout determines that the end state will be the communal
+ * scene. The value also does not change while flinging away until the target scene is no longer
+ * communal.
+ *
+ * If you need a flow that is only true when communal is fully showing and not in transition,
+ * use [isIdleOnCommunal].
*/
+ // TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
@@ -146,6 +155,16 @@
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
+ /**
+ * Flow that emits a boolean if any portion of the communal UI is visible at all.
+ *
+ * This flow will be true during any transition and when idle on the communal scene.
+ */
+ val isCommunalVisible: Flow<Boolean> =
+ communalRepository.transitionState.map {
+ !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+ }
+
/** 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/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 61db026..5f1d89e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -22,14 +22,31 @@
import android.content.Context
import android.os.Looper
import android.widget.RemoteViews
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
class CommunalAppWidgetHost(
context: Context,
+ private val backgroundScope: CoroutineScope,
hostId: Int,
interactionHandler: RemoteViews.InteractionHandler,
- looper: Looper
+ looper: Looper,
+ logBuffer: LogBuffer,
) : AppWidgetHost(context, hostId, interactionHandler, looper) {
+
+ private val logger = Logger(logBuffer, TAG)
+
+ private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
+
+ /** App widget ids that have been removed and no longer available. */
+ val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow()
+
override fun onCreateView(
context: Context,
appWidgetId: Int,
@@ -52,4 +69,15 @@
// `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
}
+
+ override fun onAppWidgetRemoved(appWidgetId: Int) {
+ backgroundScope.launch {
+ logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId }
+ _appWidgetIdToRemove.emit(appWidgetId)
+ }
+ }
+
+ companion object {
+ private const val TAG = "CommunalAppWidgetHost"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 586df32..fb9abeb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -40,6 +40,7 @@
@Background private val bgScope: CoroutineScope,
@Main private val uiDispatcher: CoroutineDispatcher
) : CoreStartable {
+
override fun start() {
or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
// Only trigger updates on state changes, ignoring the initial false value.
@@ -47,6 +48,10 @@
.filter { (previous, new) -> previous != new }
.onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
.launchIn(bgScope)
+
+ appWidgetHost.appWidgetIdToRemove
+ .onEach { appWidgetId -> communalInteractor.deleteWidget(appWidgetId) }
+ .launchIn(bgScope)
}
private suspend fun updateAppWidgetHostActive(active: Boolean) =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 54c709d..eb6dc43 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,7 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.res.R
import javax.inject.Inject
/** An Activity for editing the widgets that appear in hub mode. */
@@ -45,8 +46,10 @@
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
- private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
+ private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
+
+ private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
+
private const val TAG = "EditWidgetsActivity"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
@@ -111,11 +114,19 @@
?.let { packageName ->
try {
addWidgetActivityLauncher.launch(
- Intent(Intent.ACTION_PICK).also {
- it.setPackage(packageName)
- it.putExtra(
- EXTRA_FILTER_STRATEGY,
- FILTER_STRATEGY_GLANCEABLE_HUB
+ Intent(Intent.ACTION_PICK).apply {
+ setPackage(packageName)
+ putExtra(
+ EXTRA_DESIRED_WIDGET_WIDTH,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_width
+ )
+ )
+ putExtra(
+ EXTRA_DESIRED_WIDGET_HEIGHT,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_height
+ )
)
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8170879..5ee2045 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
+import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
@@ -51,7 +52,6 @@
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
import com.android.systemui.statusbar.notification.InstantAppNotifier
-import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
@@ -225,12 +225,6 @@
@ClassKey(WMShell::class)
abstract fun bindWMShell(sysui: WMShell): CoreStartable
- /** Inject into KeyguardLiftController. */
- @Binds
- @IntoMap
- @ClassKey(KeyguardLiftController::class)
- abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
-
/** Inject into MediaTttSenderCoordinator. */
@Binds
@IntoMap
@@ -338,4 +332,9 @@
abstract fun bindCommunalAppWidgetHostStartable(
impl: CommunalAppWidgetHostStartable
): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(HomeControlsDreamStartable::class)
+ abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2587e2d..efcbd47 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -57,6 +57,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
@@ -193,6 +194,7 @@
FlagsModule.class,
FlagDependenciesModule.class,
FooterActionsModule.class,
+ InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
KeyguardBlueprintModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinder.kt
new file mode 100644
index 0000000..1fd7d00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinder.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.ui.binder
+
+import android.content.pm.PackageManager
+import android.hardware.Sensor
+import android.hardware.TriggerEvent
+import android.hardware.TriggerEventListener
+import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.Assert
+import com.android.systemui.util.sensors.AsyncSensorManager
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Triggers face auth and active unlock on lift when the device is showing the lock screen or
+ * bouncer. Only initialized if face auth is supported on the device. Not to be confused with the
+ * lift to wake gesture which is handled by {@link com.android.server.policy.PhoneWindowManager}.
+ */
+@SysUISingleton
+class LiftToRunFaceAuthBinder
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val packageManager: PackageManager,
+ private val asyncSensorManager: AsyncSensorManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ keyguardInteractor: KeyguardInteractor,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ powerInteractor: PowerInteractor,
+) : CoreStartable {
+
+ private var pickupSensor: Sensor? = null
+ private val isListening: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val stoppedListening: Flow<Unit> = isListening.filterNot { it }.map {} // map to Unit
+
+ private val onAwakeKeyguard: Flow<Boolean> =
+ combine(
+ powerInteractor.isInteractive,
+ keyguardInteractor.isKeyguardVisible,
+ ) { isInteractive, isKeyguardVisible ->
+ isInteractive && isKeyguardVisible
+ }
+ private val bouncerShowing: Flow<Boolean> =
+ combine(
+ primaryBouncerInteractor.isShowing,
+ alternateBouncerInteractor.isVisible,
+ ) { primaryBouncerShowing, alternateBouncerShowing ->
+ primaryBouncerShowing || alternateBouncerShowing
+ }
+ private val listenForPickupSensor: Flow<Boolean> =
+ combine(
+ stoppedListening,
+ bouncerShowing,
+ onAwakeKeyguard,
+ ) { _, bouncerShowing, onAwakeKeyguard ->
+ (onAwakeKeyguard || bouncerShowing) &&
+ deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+ }
+
+ override fun start() {
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ init()
+ }
+ }
+
+ private fun init() {
+ pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
+ scope.launch {
+ listenForPickupSensor.collect { listenForPickupSensor ->
+ updateListeningState(listenForPickupSensor)
+ }
+ }
+ }
+
+ private val listener: TriggerEventListener =
+ object : TriggerEventListener() {
+ override fun onTrigger(event: TriggerEvent?) {
+ Assert.isMainThread()
+ deviceEntryFaceAuthInteractor.onDeviceLifted()
+ keyguardUpdateMonitor.requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
+ "KeyguardLiftController"
+ )
+
+ // Not listening anymore since trigger events unregister themselves
+ isListening.value = false
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("LiftToRunFaceAuthBinder:")
+ pw.println(" pickupSensor: $pickupSensor")
+ pw.println(" isListening: ${isListening.value}")
+ }
+
+ private fun updateListeningState(shouldListen: Boolean) {
+ if (pickupSensor == null) {
+ return
+ }
+ if (shouldListen != isListening.value) {
+ isListening.value = shouldListen
+
+ if (shouldListen) {
+ asyncSensorManager.requestTriggerSensor(listener, pickupSensor)
+ } else {
+ asyncSensorManager.cancelTriggerSensor(listener, pickupSensor)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 0656933..ba74742 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.dagger;
import android.annotation.Nullable;
+import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -30,12 +31,18 @@
import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
+import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
+import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
+import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -88,6 +95,15 @@
}
/**
+ * Provides Home Controls Dream Service
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(HomeControlsDreamService.class)
+ Service bindHomeControlsDreamService(
+ HomeControlsDreamService service);
+
+ /**
* Provides a touch inset manager for dreams.
*/
@Provides
@@ -151,4 +167,9 @@
static String providesDreamOverlayWindowTitle(@Main Resources resources) {
return resources.getString(R.string.app_label);
}
+
+ /** Provides activity for dream service */
+ @Binds
+ DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
new file mode 100644
index 0000000..b35b7f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+
+fun interface DreamActivityProvider {
+ /**
+ * Provides abstraction for getting the activity associated with a dream service, so that the
+ * activity can be mocked in tests.
+ */
+ fun getActivity(dreamService: DreamService): Activity?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
new file mode 100644
index 0000000..0854e93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+import javax.inject.Inject
+
+class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
+ override fun getActivity(dreamService: DreamService): Activity {
+ return dreamService.activity
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
new file mode 100644
index 0000000..e04a505
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols
+
+import android.content.Intent
+import android.service.controls.ControlsProviderService
+import android.service.dreams.DreamService
+import android.window.TaskFragmentInfo
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dreams.DreamLogger
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DreamLog
+import javax.inject.Inject
+
+class HomeControlsDreamService
+@Inject
+constructor(
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val taskFragmentFactory: TaskFragmentComponent.Factory,
+ private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ private val dreamActivityProvider: DreamActivityProvider,
+ @DreamLog logBuffer: LogBuffer
+) : DreamService() {
+ private lateinit var taskFragmentComponent: TaskFragmentComponent
+
+ private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ val activity = dreamActivityProvider.getActivity(this)
+ if (activity == null) {
+ finish()
+ return
+ }
+ taskFragmentComponent =
+ taskFragmentFactory
+ .create(
+ activity = activity,
+ onCreateCallback = this::onTaskFragmentCreated,
+ onInfoChangedCallback = this::onTaskFragmentInfoChanged,
+ hide = { finish() }
+ )
+ .apply { createTaskFragment() }
+ }
+
+ private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
+ if (taskFragmentInfo.isEmpty) {
+ logger.d("Finishing dream due to TaskFragment being empty")
+ finish()
+ }
+ }
+
+ private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) {
+ val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+ val componentName = homeControlsComponentInteractor.panelComponent.value
+ logger.d("Starting embedding $componentName")
+ val intent =
+ Intent().apply {
+ component = componentName
+ putExtra(ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, setting)
+ putExtra(
+ ControlsProviderService.EXTRA_CONTROLS_SURFACE,
+ ControlsProviderService.CONTROLS_SURFACE_DREAM
+ )
+ }
+ taskFragmentComponent.startActivityInTaskFragment(intent)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ taskFragmentComponent.destroy()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
new file mode 100644
index 0000000..6cd94c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.service.controls.flags.Flags.homePanelDream
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class HomeControlsDreamStartable
+@Inject
+constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+
+ private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+
+ override fun start() {
+ if (!homePanelDream()) return
+ bgScope.launch {
+ homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+ setEnableHomeControlPanel(selectedPanelComponent != null)
+ }
+ }
+ }
+
+ private fun setEnableHomeControlPanel(enabled: Boolean) {
+ val packageState =
+ if (enabled) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ }
+ packageManager.setComponentEnabledSetting(
+ componentName,
+ packageState,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
new file mode 100644
index 0000000..6f7dcb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols
+
+import android.app.Activity
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Binder
+import android.window.TaskFragmentCreationParams
+import android.window.TaskFragmentInfo
+import android.window.TaskFragmentOperation
+import android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
+import android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
+import android.window.TaskFragmentOrganizer
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN
+import android.window.TaskFragmentTransaction
+import android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED
+import android.window.WindowContainerTransaction
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+typealias FragmentInfoCallback = (TaskFragmentInfo) -> Unit
+
+/** Wrapper around TaskFragmentOrganizer for managing a task fragment within an activity */
+class TaskFragmentComponent
+@AssistedInject
+constructor(
+ @Assisted private val activity: Activity,
+ @Assisted("onCreateCallback") private val onCreateCallback: FragmentInfoCallback,
+ @Assisted("onInfoChangedCallback") private val onInfoChangedCallback: FragmentInfoCallback,
+ @Assisted private val hide: () -> Unit,
+ @Main private val executor: DelayableExecutor,
+) {
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ activity: Activity,
+ @Assisted("onCreateCallback") onCreateCallback: FragmentInfoCallback,
+ @Assisted("onInfoChangedCallback") onInfoChangedCallback: FragmentInfoCallback,
+ hide: () -> Unit
+ ): TaskFragmentComponent
+ }
+
+ private val fragmentToken = Binder()
+ private val organizer: TaskFragmentOrganizer =
+ object : TaskFragmentOrganizer(executor) {
+
+ override fun onTransactionReady(transaction: TaskFragmentTransaction) {
+ handleTransactionReady(transaction)
+ }
+ }
+ .apply { registerOrganizer(true /* isSystemOrganizer */) }
+
+ private fun handleTransactionReady(transaction: TaskFragmentTransaction) {
+ val resultT = WindowContainerTransaction()
+
+ for (change in transaction.changes) {
+ change.taskFragmentInfo?.let { taskFragmentInfo ->
+ if (taskFragmentInfo.fragmentToken == fragmentToken) {
+ when (change.type) {
+ TYPE_TASK_FRAGMENT_APPEARED -> {
+ resultT.addTaskFragmentOperation(
+ fragmentToken,
+ TaskFragmentOperation.Builder(OP_TYPE_REORDER_TO_TOP_OF_TASK)
+ .build()
+ )
+
+ onCreateCallback(taskFragmentInfo)
+ }
+ TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
+ onInfoChangedCallback(taskFragmentInfo)
+ }
+ TYPE_TASK_FRAGMENT_VANISHED -> {
+ hide()
+ }
+ TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
+ TYPE_TASK_FRAGMENT_ERROR -> {
+ hide()
+ }
+ TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
+ else ->
+ throw IllegalArgumentException(
+ "Unknown TaskFragmentEvent=" + change.type
+ )
+ }
+ }
+ }
+ }
+ organizer.onTransactionHandled(
+ transaction.transactionToken,
+ resultT,
+ TASK_FRAGMENT_TRANSIT_CHANGE,
+ false
+ )
+ }
+
+ /** Creates the task fragment */
+ fun createTaskFragment() {
+ val taskBounds = Rect(activity.resources.configuration.windowConfiguration.bounds)
+ val fragmentOptions =
+ TaskFragmentCreationParams.Builder(
+ organizer.organizerToken,
+ fragmentToken,
+ activity.activityToken!!
+ )
+ .setInitialRelativeBounds(taskBounds)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ organizer.applyTransaction(
+ WindowContainerTransaction().createTaskFragment(fragmentOptions),
+ TASK_FRAGMENT_TRANSIT_CHANGE,
+ false
+ )
+ }
+
+ private fun WindowContainerTransaction.startActivity(intent: Intent) =
+ this.startActivityInTaskFragment(fragmentToken, activity.activityToken!!, intent, null)
+
+ /** Starts the provided activity in the fragment and move it to the background */
+ fun startActivityInTaskFragment(intent: Intent) {
+ organizer.applyTransaction(
+ WindowContainerTransaction().startActivity(intent),
+ TASK_FRAGMENT_TRANSIT_OPEN,
+ false
+ )
+ }
+
+ /** Destroys the task fragment */
+ fun destroy() {
+ organizer.applyTransaction(
+ WindowContainerTransaction()
+ .addTaskFragmentOperation(
+ fragmentToken,
+ TaskFragmentOperation.Builder(OP_TYPE_DELETE_TASK_FRAGMENT).build()
+ ),
+ TASK_FRAGMENT_TRANSIT_CLOSE,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
new file mode 100644
index 0000000..91e0547
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols.domain.interactor
+
+import android.content.ComponentName
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class HomeControlsComponentInteractor
+@Inject
+constructor(
+ private val selectedComponentRepository: SelectedComponentRepository,
+ private val controlsComponent: ControlsComponent,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ userRepository: UserRepository,
+ @Background private val bgScope: CoroutineScope
+) {
+ private val controlsListingController =
+ controlsComponent.getControlsListingController().getOrNull()
+
+ /** Gets the current user's selected panel, or null if there isn't one */
+ private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { user ->
+ selectedComponentRepository.selectedComponentFlow(user.userHandle)
+ }
+ .map { if (it?.isPanel == true) it else null }
+
+ /** Gets all the available panels which are authorized by the user */
+ private fun allPanelItem(): Flow<List<PanelComponent>> {
+ if (controlsListingController == null) {
+ return emptyFlow()
+ }
+ return conflatedCallbackFlow {
+ val listener =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ trySend(serviceInfos)
+ }
+ }
+ controlsListingController.addCallback(listener)
+ awaitClose { controlsListingController.removeCallback(listener) }
+ }
+ .onStart { emit(controlsListingController.getCurrentServices()) }
+ .map { serviceInfos ->
+ val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
+ }
+ }
+ }
+ }
+ val panelComponent: StateFlow<ComponentName?> =
+ combine(allPanelItem(), selectedItem) { items, selected ->
+ val item =
+ items.firstOrNull { it.componentName == selected?.componentName }
+ ?: items.firstOrNull()
+ item?.panelActivity
+ }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+
+ data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index c9b56a2..05279fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -23,7 +23,6 @@
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -34,17 +33,14 @@
/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
public class CommunalTouchHandler implements DreamTouchHandler {
private final int mInitiationWidth;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
private final Optional<CentralSurfaces> mCentralSurfaces;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
@Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
- mNotificationShadeWindowController = notificationShadeWindowController;
}
@Override
@@ -60,9 +56,8 @@
}
private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
- // Force the notification shade window open (otherwise the hub won't show while swiping).
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
-
+ // Notification shade window has its own logic to be visible if the hub is open, no need to
+ // do anything here other than send touch events over.
session.registerInputListener(ev -> {
surfaces.handleDreamTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 1540423..df0566e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -29,6 +29,8 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject
@@ -45,6 +47,7 @@
// Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
+ NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
// Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt
new file mode 100644
index 0000000..bac48f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod
+
+import com.android.systemui.inputmethod.data.repository.InputMethodRepositoryModule
+import dagger.Module
+
+/** Module for providing objects exposed by the input method package. */
+@Module(
+ includes =
+ [
+ InputMethodRepositoryModule::class,
+ ],
+)
+object InputMethodModule
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
new file mode 100644
index 0000000..bdc18b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.model
+
+/**
+ * Models an input method editor (IME).
+ *
+ * @see android.view.inputmethod.InputMethodInfo
+ */
+data class InputMethodModel(
+ /** A unique ID for this input method. */
+ val imeId: String,
+ /** The subtypes of this IME (may be empty). */
+ val subtypes: List<Subtype>,
+) {
+ /**
+ * A Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard), and is
+ * used for IME switch and settings.
+ *
+ * @see android.view.inputmethod.InputMethodSubtype
+ */
+ data class Subtype(
+ /** A unique ID for this IME subtype. */
+ val subtypeId: Int,
+ /**
+ * Whether this subtype is auxiliary. An auxiliary subtype will not be shown in the list of
+ * enabled IMEs for choosing the current IME in Settings, but it will be shown in the list
+ * of IMEs in the IME switcher to allow the user to tentatively switch to this subtype while
+ * an IME is shown.
+ *
+ * The intent of this flag is to allow for IMEs that are invoked in a one-shot way as
+ * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice
+ * input).
+ */
+ val isAuxiliary: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
new file mode 100644
index 0000000..5f316c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import android.annotation.SuppressLint
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Provides access to input-method related application state in the bouncer. */
+interface InputMethodRepository {
+ /**
+ * Creates and returns a new `Flow` of installed input methods that are enabled for the
+ * specified user.
+ *
+ * @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
+ * call for each IME, avoid if not needed).
+ * @see InputMethodManager.getEnabledInputMethodListAsUser
+ */
+ suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+
+ /** Returns enabled subtypes for the currently selected input method. */
+ suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+
+ /**
+ * Shows the system's input method picker dialog.
+ *
+ * @param displayId The display ID on which to show the dialog.
+ * @param showAuxiliarySubtypes Whether to show auxiliary input method subtypes in the list of
+ * enabled IMEs.
+ * @see InputMethodManager.showInputMethodPickerFromSystem
+ */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean)
+}
+
+@SysUISingleton
+class InputMethodRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val inputMethodManager: InputMethodManager,
+) : InputMethodRepository {
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean
+ ): Flow<InputMethodModel> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ }
+ .asFlow()
+ .map { inputMethodInfo ->
+ InputMethodModel(
+ imeId = inputMethodInfo.id,
+ subtypes =
+ if (fetchSubtypes) {
+ enabledInputMethodSubtypes(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes = true
+ )
+ } else {
+ listOf()
+ }
+ )
+ }
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
+ return enabledInputMethodSubtypes(
+ inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
+
+ @SuppressLint("MissingPermission")
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ withContext(backgroundDispatcher) {
+ inputMethodManager.showInputMethodPickerFromSystem(showAuxiliarySubtypes, displayId)
+ }
+ }
+
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ *
+ * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
+ * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
+ * subtypes. If an input method info doesn't have enabled subtypes, the framework will
+ * implicitly enable subtypes according to the current system language.
+ * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ */
+ private suspend fun enabledInputMethodSubtypes(
+ inputMethodInfo: InputMethodInfo?,
+ allowsImplicitlyEnabledSubtypes: Boolean
+ ): List<InputMethodModel.Subtype> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes
+ )
+ }
+ .map {
+ InputMethodModel.Subtype(
+ subtypeId = it.subtypeId,
+ isAuxiliary = it.isAuxiliary,
+ )
+ }
+ }
+}
+
+@Module
+interface InputMethodRepositoryModule {
+ @Binds fun repository(impl: InputMethodRepositoryImpl): InputMethodRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
new file mode 100644
index 0000000..c54aa7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.repository.InputMethodRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+
+/** Hosts application business logic related to input methods (e.g. software keyboard). */
+@SysUISingleton
+class InputMethodInteractor
+@Inject
+constructor(
+ private val repository: InputMethodRepository,
+) {
+ /**
+ * Returns whether there are multiple enabled input methods to choose from for password input.
+ *
+ * Method adapted from `com.android.inputmethod.latin.Utils`.
+ */
+ suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ // Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
+ val matchingInputMethods =
+ repository
+ .enabledInputMethods(userId, fetchSubtypes = true)
+ .filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
+ .take(2) // Short-circuit if we find at least 2 matching IMEs.
+
+ return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ }
+
+ /** Shows the system's input method picker dialog. */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ repository.showInputMethodPicker(displayId, showAuxiliarySubtypes)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index 26e83a3..f16a3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyboard
-import com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag
+import android.hardware.input.InputSettings
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
@@ -40,7 +40,7 @@
if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
keyboardBacklightDialogCoordinator.get().startListening()
}
- if (keyboardA11yStickyKeysFlag()) {
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
stickyKeysIndicatorCoordinator.get().startListening()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index b68551b..c3a618d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -58,7 +58,6 @@
dialog = null
} else if (dialog == null) {
dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
- setCanceledOnTouchOutside(false)
window?.setAttributes()
show()
}
@@ -70,6 +69,7 @@
private fun Window.setAttributes() {
setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
setGravity(Gravity.TOP or Gravity.END)
attributes = WindowManager.LayoutParams().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f10b87e..b5f9c69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -85,10 +85,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index afef875..7f43fac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,6 +41,7 @@
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -86,6 +87,7 @@
private val vibratorHelper: VibratorHelper,
private val falsingManager: FalsingManager,
private val aodAlphaViewModel: AodAlphaViewModel,
+ private val keyguardClockViewModel: KeyguardClockViewModel,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -113,7 +115,11 @@
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel
+ )
}
keyguardBlueprintCommandListener.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 13e3835..e16f8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -51,7 +51,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
-import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
@@ -104,7 +104,7 @@
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
- KeyguardFaceAuthModule.class,
+ DeviceEntryFaceAuthModule.class,
KeyguardDisplayModule.class,
StartKeyguardTransitionModule.class,
ResourceTrimmerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthModule.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthModule.kt
index fede479..4cd544f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepositoryImpl
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.ui.binder.LiftToRunFaceAuthBinder
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
@@ -32,7 +33,7 @@
import dagger.multibindings.IntoMap
@Module
-interface KeyguardFaceAuthModule {
+interface DeviceEntryFaceAuthModule {
@Binds
fun deviceEntryFaceAuthRepository(
impl: DeviceEntryFaceAuthRepositoryImpl
@@ -41,13 +42,20 @@
@Binds
@IntoMap
@ClassKey(SystemUIDeviceEntryFaceAuthInteractor::class)
- fun bind(impl: SystemUIDeviceEntryFaceAuthInteractor): CoreStartable
+ fun bindSystemUIDeviceEntryFaceAuthInteractor(
+ impl: SystemUIDeviceEntryFaceAuthInteractor
+ ): CoreStartable
@Binds
fun keyguardFaceAuthInteractor(
impl: SystemUIDeviceEntryFaceAuthInteractor
): DeviceEntryFaceAuthInteractor
+ @Binds
+ @IntoMap
+ @ClassKey(LiftToRunFaceAuthBinder::class)
+ fun bindLiftToRunFaceAuthBinder(impl: LiftToRunFaceAuthBinder): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 48b6634..9381830 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -23,6 +23,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
@@ -54,6 +56,8 @@
TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
/**
@@ -103,7 +107,12 @@
/** Re-emits the last emitted blueprint value if possible. */
fun refreshBlueprint() {
+ refreshBlueprintWithTransition(DefaultTransition)
+ }
+
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
refreshBluePrint.tryEmit(Unit)
+ refreshBlueprintTransition.tryEmit(type)
}
/** Prints all available blueprints to the PrintWriter. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 9d38be9..71d941a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleMultiple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -64,6 +65,7 @@
listenForHubToAlternateBouncer()
listenForHubToOccluded()
listenForHubToGone()
+ listenForHubToDreaming()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -131,6 +133,23 @@
}
}
+ private fun listenForHubToDreaming() {
+ val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+ scope.launch("$TAG#listenForHubToDreaming") {
+ keyguardInteractor.isAbleToDream
+ .sampleMultiple(startedKeyguardTransitionStep, finishedKeyguardState)
+ .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) ->
+ val isOnHub = finishedKeyguardState == KeyguardState.GLANCEABLE_HUB
+ val isTransitionInterruptible =
+ lastStartedTransition.to == KeyguardState.GLANCEABLE_HUB &&
+ !invalidFromStates.contains(lastStartedTransition.from)
+ if (isAbleToDream && (isOnHub || isTransitionInterruptible)) {
+ startTransitionTo(KeyguardState.DREAMING)
+ }
+ }
+ }
+ }
+
private fun listenForHubToOccluded() {
scope.launch {
keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
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 3965648..deb70b7 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
@@ -405,7 +405,9 @@
interpolator = Interpolators.LINEAR
duration =
when (toState) {
- KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ // Adds 100ms to the overall delay to workaround legacy setOccluded calls
+ // being delayed in KeyguardViewMediator
+ KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index ba44e68..566e006 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -24,12 +24,13 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -48,13 +49,15 @@
*
* This flow can also emit the same blueprint value if refreshBlueprint is emitted.
*/
- val blueprint: Flow<KeyguardBlueprint> =
- merge(
- keyguardBlueprintRepository.blueprint,
- keyguardBlueprintRepository.refreshBluePrint.map {
- keyguardBlueprintRepository.blueprint.value
- }
- )
+ val blueprint: Flow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
+
+ val blueprintWithTransition =
+ combine(
+ keyguardBlueprintRepository.refreshBluePrint,
+ keyguardBlueprintRepository.refreshBlueprintTransition
+ ) { _, source ->
+ source
+ }
init {
applicationScope.launch {
@@ -107,6 +110,10 @@
keyguardBlueprintRepository.refreshBlueprint()
}
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
+ keyguardBlueprintRepository.refreshBlueprintWithTransition(type)
+ }
+
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
new file mode 100644
index 0000000..a0f9be6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */
+data class Edge(
+ val from: KeyguardState?,
+ val to: KeyguardState?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 4abda74..00b7989 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
@@ -174,11 +175,6 @@
}
}
- data class Edge(
- val from: KeyguardState?,
- val to: KeyguardState?,
- )
-
data class StateToValue(
val transitionState: TransitionState,
val value: Float?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 703bb87..873cc84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.content.res.ColorStateList
+import android.util.StateSet
import android.view.HapticFeedbackConstants
import android.view.View
import androidx.lifecycle.Lifecycle
@@ -113,6 +114,8 @@
fgIconView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Start with an empty state
+ fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
launch {
fgViewModel.viewModel.collect { viewModel ->
fgIconView.setImageState(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 8d6493f..404046b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -26,7 +26,10 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
@@ -34,7 +37,11 @@
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
- fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+ fun bind(
+ constraintLayout: ConstraintLayout,
+ viewModel: KeyguardBlueprintViewModel,
+ clockViewModel: KeyguardClockViewModel
+ ) {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
@@ -42,6 +49,7 @@
val prevBluePrint = viewModel.currentBluePrint
Trace.beginSection("KeyguardBlueprint#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
+ TransitionManager.endTransitions(constraintLayout)
val cs =
ConstraintSet().apply {
@@ -54,11 +62,28 @@
}
// Apply transition.
- if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
- prevBluePrint != blueprint) {
+ if (
+ !keyguardBottomAreaRefactor() &&
+ prevBluePrint != null &&
+ prevBluePrint != blueprint
+ ) {
TransitionManager.beginDelayedTransition(
constraintLayout,
- BaseBlueprintTransition()
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
+ )
+ )
+ } else {
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
)
}
@@ -71,6 +96,25 @@
Trace.endSection()
}
}
+
+ launch {
+ viewModel.blueprintWithTransition.collect { source ->
+ TransitionManager.endTransitions(constraintLayout)
+
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ viewModel.currentBluePrint?.applyConstraints(this)
+ }
+
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(source, clockViewModel)
+ )
+ cs.applyTo(constraintLayout)
+ Trace.endSection()
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 3c3ebdf..f0e89f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -16,34 +16,30 @@
package com.android.systemui.keyguard.ui.binder
-import android.animation.Animator
-import android.animation.ValueAnimator
-import android.transition.Transition
import android.transition.TransitionManager
import android.transition.TransitionSet
-import android.transition.TransitionValues
-import android.view.ViewGroup
+import android.util.Log
+import android.view.View.INVISIBLE
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
-private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
-
object KeyguardClockViewBinder {
@JvmStatic
fun bind(
@@ -67,32 +63,56 @@
viewModel.clock = currentClock
addClockViews(currentClock, keyguardRootView)
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ applyConstraints(clockSection, keyguardRootView, true)
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.ClockSize
+ )
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.clockShouldBeCentered.collect {
+ viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+ Log.d(
+ "ClockViewBinder",
+ "Sherry clockShouldBeCentered $clockShouldBeCentered"
+ )
viewModel.clock?.let {
- if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
- playClockCenteringAnimation(clockSection, keyguardRootView, it)
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ // TODO(b/323020908): remove ID check
+ if (
+ it.largeClock.config.hasCustomPositionUpdatedAnimation &&
+ it.config.id == DEFAULT_CLOCK_ID
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultClockStepping
+ )
} else {
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
}
}
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.isAodIconsVisible.collect {
- applyConstraints(clockSection, keyguardRootView, true)
+ viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+ viewModel.clock?.let {
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ if (
+ viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
+ }
+ }
}
}
}
@@ -155,7 +175,9 @@
}
// small clock should either be a single view or container with id
// `lockscreen_clock_view`
- clock.smallClock.layout.views.forEach { rootView.addView(it) }
+ clock.smallClock.layout.views.forEach {
+ rootView.addView(it).apply { it.visibility = INVISIBLE }
+ }
clock.largeClock.layout.views.forEach { rootView.addView(it) }
}
}
@@ -173,74 +195,4 @@
}
constraintSet.applyTo(rootView)
}
-
- private fun playClockCenteringAnimation(
- clockSection: ClockSection,
- keyguardRootView: ConstraintLayout,
- clock: ClockController,
- ) {
- // Find the clock, so we can exclude it from this transition.
- val clockView = clock.largeClock.view
- val set = TransitionSet()
- val adapter = SplitShadeTransitionAdapter(clock)
- adapter.setInterpolator(Interpolators.LINEAR)
- adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS)
- adapter.addTarget(clockView)
- set.addTransition(adapter)
- applyConstraints(clockSection, keyguardRootView, true, set)
- }
-
- internal class SplitShadeTransitionAdapter
- @VisibleForTesting
- constructor(private val clock: ClockController) : Transition() {
- private fun captureValues(transitionValues: TransitionValues) {
- transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
- val locationInWindowTmp = IntArray(2)
- transitionValues.view.getLocationInWindow(locationInWindowTmp)
- transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
- }
-
- override fun captureEndValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun captureStartValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun createAnimator(
- sceneRoot: ViewGroup,
- startValues: TransitionValues?,
- endValues: TransitionValues?
- ): Animator? {
- if (startValues == null || endValues == null) {
- return null
- }
- val anim = ValueAnimator.ofFloat(0f, 1f)
- val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
- val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
- val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
- // Using windowX, to determine direction, instead of left, as in RTL the difference of
- // toLeft - fromLeft is always positive, even when moving left.
- val direction = if (toWindowX - fromWindowX > 0) 1 else -1
- anim.addUpdateListener { animation: ValueAnimator ->
- clock.largeClock.animations.onPositionUpdated(
- fromLeft,
- direction,
- animation.animatedFraction
- )
- }
- return anim
- }
-
- override fun getTransitionProperties(): Array<String> {
- return TRANSITION_PROPERTIES
- }
-
- companion object {
- private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
- private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
- private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 10392e3..08a2b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -49,14 +49,15 @@
clockViewModel,
smartspaceViewModel
)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
launch {
+ if (!migrateClocksToBlueprint()) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index fd530b7..9c9df80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -19,30 +19,32 @@
import android.animation.Animator
import android.animation.ObjectAnimator
import android.transition.ChangeBounds
+import android.transition.Transition
import android.transition.TransitionSet
import android.transition.TransitionValues
import android.transition.Visibility
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
-import com.android.systemui.res.R
-class BaseBlueprintTransition : TransitionSet() {
+class BaseBlueprintTransition(val clockViewModel: KeyguardClockViewModel) : TransitionSet() {
init {
ordering = ORDERING_SEQUENTIAL
addTransition(AlphaOutVisibility())
.addTransition(ChangeBounds())
.addTransition(AlphaInVisibility())
excludeTarget(Layer::class.java, /* exclude= */ true)
- excludeClockAndSmartspaceViews()
+ excludeClockAndSmartspaceViews(this)
}
- private fun excludeClockAndSmartspaceViews() {
- excludeTarget(R.id.lockscreen_clock_view, true)
- excludeTarget(R.id.lockscreen_clock_view_large, true)
- excludeTarget(SmartspaceView::class.java, true)
- // TODO(b/319468190): need to exclude views from large weather clock
+ private fun excludeClockAndSmartspaceViews(transition: Transition) {
+ transition.excludeTarget(SmartspaceView::class.java, true)
+ clockViewModel.clock?.let { clock ->
+ clock.largeClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ clock.smallClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ }
}
class AlphaOutVisibility : Visibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
new file mode 100644
index 0000000..524aa1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions
+
+import android.transition.TransitionSet
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+
+enum class IntraBlueprintTransitionType {
+ ClockSize,
+ ClockCenter,
+ DefaultClockStepping,
+ DefaultTransition,
+ AodNotifIconsTransition,
+ // When transition between blueprint, we don't need any duration or interpolator but we need
+ // all elements go to correct state
+ NoTransition,
+}
+
+class IntraBlueprintTransition(
+ type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ if (type == IntraBlueprintTransitionType.DefaultClockStepping)
+ addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) })
+ addTransition(ClockSizeTransition(type, clockViewModel))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index fe4f07d..b1178f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -166,10 +166,7 @@
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
- if (!keyguardClockViewModel.useLargeClock) {
- largeClockTopMargin -=
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
- }
+
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 400d0dc..a651c10 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -87,7 +87,8 @@
return
}
// This moves the existing NSSL view to a different parent, as the controller is a
- // singleton and recreating it has other bad side effects
+ // singleton and recreating it has other bad side effects.
+ // In the SceneContainer, this is done by the NotificationSection composable.
notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
(it.parent as ViewGroup).removeView(it)
sharedNotificationContainer.addNotificationStackScrollLayout(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
new file mode 100644
index 0000000..99565b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.transition.ChangeBounds
+import android.transition.TransitionSet
+import android.transition.TransitionValues
+import android.transition.Visibility
+import android.view.View
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
+
+const val CLOCK_OUT_MILLIS = 133L
+const val CLOCK_IN_MILLIS = 167L
+val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
+const val CLOCK_IN_START_DELAY_MILLIS = 133L
+val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
+
+class ClockSizeTransition(
+ val type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ addTransition(ClockOutTransition(clockViewModel, type))
+ addTransition(ClockInTransition(clockViewModel, type))
+ addTransition(SmartspaceChangeBounds(clockViewModel, type))
+ addTransition(ClockInChangeBounds(clockViewModel, type))
+ addTransition(ClockOutChangeBounds(clockViewModel, type))
+ }
+
+ class ClockInTransition(viewModel: KeyguardClockViewModel, type: IntraBlueprintTransitionType) :
+ Visibility() {
+ init {
+ mode = MODE_IN
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+
+ override fun onAppear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 1f).also {
+ it.duration = duration
+ it.startDelay = startDelay
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockOutTransition(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : Visibility() {
+ init {
+ mode = MODE_OUT
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+
+ override fun onDisappear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 0f).also {
+ it.duration = duration
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockInChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = CLOCK_IN_INTERPOLATOR
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+ }
+
+ class ClockOutChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+ }
+
+ class SmartspaceChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ val type: IntraBlueprintTransitionType = IntraBlueprintTransitionType.DefaultTransition
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration =
+ if (viewModel.useLargeClock) {
+ STATUS_AREA_MOVE_UP_MILLIS
+ } else {
+ STATUS_AREA_MOVE_DOWN_MILLIS
+ }
+ interpolator = Interpolators.EMPHASIZED
+ } else {
+ duration = 0
+ }
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ addTarget(sharedR.id.bc_smartspace_view)
+ }
+
+ companion object {
+ const val STATUS_AREA_MOVE_UP_MILLIS = 967L
+ const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
new file mode 100644
index 0000000..c35dad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.plugins.clocks.ClockController
+
+class DefaultClockSteppingTransition(private val clock: ClockController) : Transition() {
+ init {
+ interpolator = Interpolators.LINEAR
+ duration = KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS
+ addTarget(clock.largeClock.view)
+ }
+ private fun captureValues(transitionValues: TransitionValues) {
+ transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
+ val locationInWindowTmp = IntArray(2)
+ transitionValues.view.getLocationInWindow(locationInWindowTmp)
+ transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ if (startValues == null || endValues == null) {
+ return null
+ }
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+ val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
+ val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
+ val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
+ // Using windowX, to determine direction, instead of left, as in RTL the difference of
+ // toLeft - fromLeft is always positive, even when moving left.
+ val direction = if (toWindowX - fromWindowX > 0) 1 else -1
+ anim.addUpdateListener { animation: ValueAnimator ->
+ clock.largeClock.animations.onPositionUpdated(
+ fromLeft,
+ direction,
+ animation.animatedFraction
+ )
+ }
+ return anim
+ }
+
+ override fun getTransitionProperties(): Array<String> {
+ return TRANSITION_PROPERTIES
+ }
+
+ companion object {
+ private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
+ private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
+ private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
+ private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 310ec95..ad6a36c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -74,7 +75,18 @@
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
+
+ private val padding: Flow<Int> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
+ if (udfpsSupported) {
+ udfpsOverlayInteractor.iconPadding
+ } else {
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ }
+ }
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index e2bfc36..d22856b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -26,4 +26,5 @@
constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
var currentBluePrint: KeyguardBlueprint? = null
val blueprint = keyguardBlueprintInteractor.blueprint
+ val blueprintWithTransition = keyguardBlueprintInteractor.blueprintWithTransition
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 523414c..c33ab12 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
@@ -586,7 +586,7 @@
coroutineScope.launch {
communalInteractor.isCommunalShowing.collect { value ->
isCommunalShowing = value
- updateDesiredLocation(forceNoAnimation = true)
+ updateDesiredLocation()
}
}
}
@@ -1149,13 +1149,17 @@
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+
+ // UMO should show in communal unless the shade is expanding or visible.
+ isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- // TODO(b/311234666): revisit logic once interactions between the hub and
- // shade/keyguard state are finalized
- isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
+
+ // Communal does not have its own StatusBarState so it should always have higher
+ // priority for the UMO over the lockscreen.
+ isCommunalShowing -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ac0bd29..1c37510f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -77,6 +77,7 @@
private Consumer<Boolean> mMediaVisibilityChangedListener;
@Orientation
private int mLastOrientation;
+ private int mLastScreenLayout;
private String mCachedSpecs = "";
@Nullable
private QSTileRevealController mQsTileRevealController;
@@ -93,15 +94,19 @@
public void onConfigurationChange(Configuration newConfig) {
final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
final int previousOrientation = mLastOrientation;
+ final int previousScreenLayout = mLastScreenLayout;
mShouldUseSplitNotificationShade = mSplitShadeStateController
.shouldUseSplitNotificationShade(getResources());
mLastOrientation = newConfig.orientation;
+ mLastScreenLayout = newConfig.screenLayout;
mQSLogger.logOnConfigurationChanged(
/* oldOrientation= */ previousOrientation,
/* newOrientation= */ mLastOrientation,
/* oldShouldUseSplitShade= */ previousSplitShadeState,
/* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+ /* oldScreenLayout= */ previousScreenLayout,
+ /* newScreenLayout= */ mLastScreenLayout,
/* containerName= */ mView.getDumpableTag());
switchTileLayoutIfNeeded();
@@ -198,6 +203,7 @@
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
+ mLastScreenLayout = getResources().getConfiguration().screenLayout;
mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
switchTileLayout(true);
@@ -443,11 +449,13 @@
}
boolean shouldUseHorizontalLayout() {
- if (mShouldUseSplitNotificationShade) {
+ if (mShouldUseSplitNotificationShade) {
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE
+ && (mLastScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
+ == Configuration.SCREENLAYOUT_LONG_YES;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 38e7972..b515ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -19,6 +19,8 @@
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Configuration.Orientation
+import android.content.res.Configuration.SCREENLAYOUT_LONG_NO
+import android.content.res.Configuration.SCREENLAYOUT_LONG_YES
import android.service.quicksettings.Tile
import android.view.View
import com.android.systemui.log.ConstantStringsLogger
@@ -266,8 +268,10 @@
fun logOnConfigurationChanged(
@Orientation oldOrientation: Int,
@Orientation newOrientation: Int,
- newShouldUseSplitShade: Boolean,
oldShouldUseSplitShade: Boolean,
+ newShouldUseSplitShade: Boolean,
+ oldScreenLayout: Int,
+ newScreenLayout: Int,
containerName: String
) {
configChangedBuffer.log(
@@ -277,6 +281,8 @@
str1 = containerName
int1 = oldOrientation
int2 = newOrientation
+ long1 = oldScreenLayout.toLong()
+ long2 = newScreenLayout.toLong()
bool1 = oldShouldUseSplitShade
bool2 = newShouldUseSplitShade
},
@@ -284,6 +290,8 @@
"config change: " +
"$str1 orientation=${toOrientationString(int2)} " +
"(was ${toOrientationString(int1)}), " +
+ "screen layout=${toScreenLayoutString(long1.toInt())} " +
+ "(was ${toScreenLayoutString(long2.toInt())}), " +
"splitShade=$bool2 (was $bool1)"
}
)
@@ -370,3 +378,11 @@
else -> "undefined"
}
}
+
+private inline fun toScreenLayoutString(screenLayout: Int): String {
+ return when (screenLayout) {
+ SCREENLAYOUT_LONG_YES -> "long"
+ SCREENLAYOUT_LONG_NO -> "notlong"
+ else -> "undefined"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index c5eeb2f..9fefcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -52,7 +52,7 @@
import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.qs.QSTile.AdapterState
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
@@ -506,13 +506,12 @@
state.expandedAccessibilityClassName
}
- if (state is BooleanState) {
+ if (state is AdapterState) {
val newState = state.value
if (tileState != newState) {
tileState = newState
}
}
- //
// Labels
if (!Objects.equals(label.text, state.label)) {
@@ -625,7 +624,7 @@
customDrawableView.setImageDrawable(state.sideViewCustomDrawable)
customDrawableView.visibility = VISIBLE
chevronView.visibility = GONE
- } else if (state !is BooleanState || state.forceExpandIcon) {
+ } else if (state !is AdapterState || state.forceExpandIcon) {
customDrawableView.setImageDrawable(null)
customDrawableView.visibility = GONE
chevronView.visibility = VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
new file mode 100644
index 0000000..26069c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<FontScalingTileModel> {
+
+ override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ val icon =
+ Icon.Loaded(
+ resources.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ theme,
+ ),
+ contentDescription = null
+ )
+ this.icon = { icon }
+ contentDescription = label
+ activationState = QSTileState.ActivationState.ACTIVE
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 0000000..745e6a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+ QSTileDataInteractor<FontScalingTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 0000000..b6f4afb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val coroutineContext: CoroutineContext,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // We animate from the touched view only if we are not on the keyguard
+ val animateFromView: Boolean =
+ action.view != null && !keyguardStateController.isShowing
+ val runnable = Runnable {
+ val dialog: SystemUIDialog =
+ fontScalingDialogDelegateProvider.get().createDialog()
+ if (animateFromView) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ action.view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } else {
+ dialog.show()
+ }
+ }
+
+ withContext(coroutineContext) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ runnable,
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+ )
+ }
+ }
+ }
+ companion object {
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
new file mode 100644
index 0000000..76042df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.model
+
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 4780a2e..5a389f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -227,7 +227,7 @@
): QSTile.State =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
- QSTile.BooleanState().apply {
+ QSTile.AdapterState().apply {
spec = config.tileSpec.spec
label = viewModelState.label
// This value is synthetic and doesn't have any meaning. It's only needed to satisfy
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4e89fbf..7d86a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -513,10 +513,6 @@
notifySystemUiStateFlags(mSysUiState.getFlags());
notifyConnectionChanged();
- if (mDoneUserChanging != null) {
- mDoneUserChanging.run();
- mDoneUserChanging = null;
- }
}
@Override
@@ -571,14 +567,11 @@
}
};
- private Runnable mDoneUserChanging;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
- public void onUserChanging(int newUser, @NonNull Context userContext,
- @NonNull Runnable resultCallback) {
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
mConnectionBackoffAttempts = 0;
- mDoneUserChanging = resultCallback;
internalConnectToCurrentUser("User changed");
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 246ccb1..37abc40 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -42,6 +42,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -55,6 +56,7 @@
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -81,6 +83,7 @@
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
+ private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
) : CoreStartable {
override fun start() {
@@ -112,26 +115,33 @@
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- sceneInteractor.transitionState
- .mapNotNull { state ->
- when (state) {
- is ObservableTransitionState.Idle -> {
- if (state.scene != SceneKey.Gone) {
- true to "scene is not Gone"
- } else {
- false to "scene is Gone"
+ deviceProvisioningInteractor.isFactoryResetProtectionActive
+ .flatMapLatest { isFrpActive ->
+ if (isFrpActive) {
+ flowOf(false to "Factory Reset Protection is active")
+ } else {
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != SceneKey.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == SceneKey.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
+ }
+ }
}
- }
- is ObservableTransitionState.Transition -> {
- if (state.fromScene == SceneKey.Gone) {
- true to "scene transitioning away from Gone"
- } else {
- null
- }
- }
+ .distinctUntilChanged()
}
}
- .distinctUntilChanged()
.collect { (isVisible, loggingReason) ->
sceneInteractor.setVisible(isVisible, loggingReason)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 93cfc5d..2b978b2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -97,6 +97,10 @@
val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
view.addView(createVisibilityToggleView(legacyView))
+ // This moves the SharedNotificationContainer to the WindowRootView just after
+ // the SceneContainerView. This SharedNotificationContainer should contain NSSL
+ // due to the NotificationStackScrollLayoutSection (legacy) or
+ // NotificationSection (scene container) moving it there.
if (flags.flexiNotifsEnabled()) {
(sharedNotificationContainer.parent as? ViewGroup)?.removeView(
sharedNotificationContainer
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 7aa0dad..b5a1313 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
@@ -379,10 +378,9 @@
.addExtras(extras);
// Add thumbnail if available
- Bitmap thumbnailBitmap = recording.getThumbnail();
- if (thumbnailBitmap != null) {
+ if (recording.getThumbnail() != null) {
Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
- .bigPicture(thumbnailBitmap)
+ .bigPicture(recording.getThumbnail())
.showBigPictureWhenCollapsed(true);
builder.setStyle(pictureStyle);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index a170d0da..5469a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -23,10 +23,12 @@
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
@@ -51,6 +53,7 @@
import android.view.Surface;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
import java.io.Closeable;
@@ -361,14 +364,27 @@
Files.copy(mTempVideoFile.toPath(), os);
os.close();
if (mTempAudioFile != null) mTempAudioFile.delete();
- DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
- Size size = new Size(metrics.widthPixels, metrics.heightPixels);
- SavedRecording recording = new SavedRecording(itemUri, mTempVideoFile, size);
+ SavedRecording recording = new SavedRecording(
+ itemUri, mTempVideoFile, getRequiredThumbnailSize());
mTempVideoFile.delete();
return recording;
}
/**
+ * Returns the required {@code Size} of the thumbnail.
+ */
+ private Size getRequiredThumbnailSize() {
+ boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+ int thumbnailIconHeight = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_height_low_ram
+ : R.dimen.notification_big_picture_max_height);
+ int thumbnailIconWidth = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_width_low_ram
+ : R.dimen.notification_big_picture_max_width);
+ return new Size(thumbnailIconWidth, thumbnailIconHeight);
+ }
+
+ /**
* Release the resources without saving the data
*/
protected void release() {
@@ -386,13 +402,14 @@
public class SavedRecording {
private Uri mUri;
- private Bitmap mThumbnailBitmap;
+ private Icon mThumbnailIcon;
protected SavedRecording(Uri uri, File file, Size thumbnailSize) {
mUri = uri;
try {
- mThumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
+ Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
file, thumbnailSize, null);
+ mThumbnailIcon = Icon.createWithBitmap(thumbnailBitmap);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail", e);
}
@@ -402,8 +419,8 @@
return mUri;
}
- public @Nullable Bitmap getThumbnail() {
- return mThumbnailBitmap;
+ public @Nullable Icon getThumbnail() {
+ return mThumbnailIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0053474..a01ac70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -331,8 +331,8 @@
);
collectFlow(
mWindowRootView,
- mCommunalInteractor.get().isCommunalShowing(),
- this::onCommunalShowingChanged
+ mCommunalInteractor.get().isCommunalVisible(),
+ this::onCommunalVisibleChanged
);
}
@@ -475,6 +475,9 @@
}
visible = true;
mLogger.d("Visibility forced to be true");
+ } else if (state.communalVisible) {
+ visible = true;
+ mLogger.d("Visibility forced to be true by communal");
}
if (mWindowRootView != null) {
if (visible) {
@@ -510,15 +513,15 @@
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean communalVisible = state.isCommunalVisibleAndNotOccluded();
final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
long timeout = -1;
- if ((communalShowing || keyguardShowing)
+ if ((communalVisible || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
if (state.bouncerShowing) {
timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
- } else if (communalShowing) {
+ } else if (communalVisible) {
timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
} else if (keyguardShowing) {
timeout = mLockScreenDisplayTimeout;
@@ -624,7 +627,7 @@
state.dozing,
state.scrimsVisibility,
state.backgroundBlurRadius,
- state.communalShowing
+ state.communalVisible
);
}
@@ -749,8 +752,8 @@
}
@VisibleForTesting
- void onCommunalShowingChanged(Boolean showing) {
- mCurrentState.communalShowing = showing;
+ void onCommunalVisibleChanged(Boolean visible) {
+ mCurrentState.communalVisible = visible;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index f9c9d83..e0a98b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,15 +58,15 @@
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
- @JvmField var communalShowing: Boolean = false,
+ @JvmField var communalVisible: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
- fun isCommunalShowingAndNotOccluded(): Boolean {
- return communalShowing && !keyguardOccluded
+ fun isCommunalVisibleAndNotOccluded(): Boolean {
+ return communalVisible && !keyguardOccluded
}
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -99,7 +99,7 @@
dozing.toString(),
scrimsVisibility.toString(),
backgroundBlurRadius.toString(),
- communalShowing.toString(),
+ communalVisible.toString(),
)
}
@@ -140,7 +140,7 @@
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
- communalShowing: Boolean,
+ communalVisible: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -172,7 +172,7 @@
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
- this.communalShowing = communalShowing
+ this.communalVisible = communalVisible
}
}
@@ -218,7 +218,7 @@
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalShowing"
+ "communalVisible"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 19a5840..ef820f3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
@@ -477,8 +478,9 @@
if (KeyguardShadeMigrationNssl.isEnabled()) {
// 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)) {
+ if (mStatusBarStateController.getState() == KEYGUARD
+ && mQuickSettingsController.shouldQuickSettingsIntercept(
+ ev.getX(), ev.getY(), 0)) {
mShadeLogger.d("NSWVC: QS intercepted");
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index d3459b1..e40bcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -46,7 +46,7 @@
/**
* Adds a listener that will be notified when the panel expansion fraction has changed and
- * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/23035507).
+ * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/281038056).
*
* @see #addExpansionListener
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index a71cf95..e619806 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,9 +25,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -44,7 +45,7 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
- deviceProvisioningRepository: DeviceProvisioningRepository,
+ deviceProvisioningInteractor: DeviceProvisioningInteractor,
disableFlagsRepository: DisableFlagsRepository,
dozeParams: DozeParameters,
keyguardRepository: KeyguardRepository,
@@ -56,7 +57,7 @@
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
combine(
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
disableFlagsRepository.disableFlags,
) { isFrpActive, isDisabledByFlags ->
isDisabledByFlags.isShadeEnabled() && !isFrpActive
@@ -83,7 +84,7 @@
powerInteractor.isAsleep,
keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
when {
// Touches are disabled when Factory Reset Protection is active
@@ -103,7 +104,7 @@
isShadeEnabled,
keyguardRepository.isDozing,
userSetupRepository.isUserSetUp,
- deviceProvisioningRepository.isDeviceProvisioned,
+ deviceProvisioningInteractor.isDeviceProvisioned,
) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
isDeviceProvisioned &&
// Disallow QS during setup if it's a simple user switcher. (The user intends to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ada7d3e..ffb11dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -56,6 +56,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -305,6 +306,13 @@
default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
default void addQsTile(ComponentName tile) { }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ default void addQsTileToFrontOrEnd(ComponentName tile, boolean end) { }
default void remQsTile(ComponentName tile) { }
default void setQsTiles(String[] tiles) {}
@@ -904,8 +912,29 @@
@Override
public void addQsTile(ComponentName tile) {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(tile, false);
+ } else {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ }
+ }
+ }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = tile;
+ args.arg2 = end;
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, args).sendToTarget();
+ }
}
}
@@ -1546,11 +1575,21 @@
mCallbacks.get(i).showPictureInPictureMenu();
}
break;
- case MSG_ADD_QS_TILE:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ case MSG_ADD_QS_TILE: {
+ if (Flags.a11yQsShortcut()) {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTileToFrontOrEnd(
+ (ComponentName) someArgs.arg1, (boolean) someArgs.arg2);
+ }
+ someArgs.recycle();
+ } else {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ }
}
break;
+ }
case MSG_REMOVE_QS_TILE:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).remQsTile((ComponentName) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index d6d3e67..04d9b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -591,8 +591,10 @@
}
private void updateLockScreenUserLockedMsg(int userId) {
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
- || mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
+ boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId);
+ boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId);
+ mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown);
+ if (!userUnlocked || encryptedOrLockdown) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dd04531..b9afb14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -617,7 +617,11 @@
private final ScrollAdapter mScrollAdapter = new ScrollAdapter() {
@Override
public boolean isScrolledToTop() {
- return mOwnScrollY == 0;
+ if (SceneContainerFlag.isEnabled()) {
+ return mController.isPlaceholderScrolledToTop();
+ } else {
+ return mOwnScrollY == 0;
+ }
}
@Override
@@ -1442,7 +1446,14 @@
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
+ // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
+ if (SceneContainerFlag.isEnabled()) {
+ // stackY should be driven by scene container, not NSSL
+ mAmbientState.setStackY(mTopPadding);
+ } else {
+ mAmbientState.setStackY(stackY);
+ }
+
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 49fde39..ed26677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1144,6 +1144,14 @@
return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
}
+ /**
+ * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled
+ * down any further.
+ */
+ public boolean isPlaceholderScrolledToTop() {
+ return mStackAppearanceInteractor.getScrolledToTop().getValue();
+ }
+
/** Set the intrinsic height of the stack content without additional padding. */
public void setIntrinsicContentHeight(float intrinsicContentHeight) {
mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index aac3c28..0197264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -44,4 +44,10 @@
* screen.
*/
val contentTop = MutableStateFlow(0f)
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop = MutableStateFlow(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 1dfde09..8307397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -42,10 +42,16 @@
* notifications, this can exceed the space available on screen to show notifications, at which
* point the notification stack should become scrollable.
*/
- val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+ val intrinsicContentHeight: StateFlow<Float> = repository.intrinsicContentHeight.asStateFlow()
/** The y-coordinate in px of top of the contents of the notification stack. */
- val contentTop = repository.contentTop.asStateFlow()
+ val contentTop: StateFlow<Float> = repository.contentTop.asStateFlow()
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
@@ -62,4 +68,9 @@
fun setContentTop(startY: Float) {
repository.contentTop.value = startY
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ repository.scrolledToTop.value = scrolledToTop
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index ed15f55..6c2cbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,7 +25,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
-import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -65,7 +64,9 @@
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
- controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
+ controller.setMaxAlphaForExpansion(
+ ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 74db583..56ff7f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -19,11 +19,15 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -32,9 +36,40 @@
constructor(
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ sceneInteractor: SceneInteractor,
) {
- /** The expansion fraction from the top of the notification shade. */
- val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+ /**
+ * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
+ * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
+ * transitioning from Shade to QuickSettings scenes.
+ */
+ val expandFraction: Flow<Float> =
+ combine(
+ shadeInteractor.shadeExpansion,
+ sceneInteractor.transitionState,
+ ) { shadeExpansion, transitionState ->
+ when (transitionState) {
+ is ObservableTransitionState.Idle -> {
+ if (transitionState.scene == SceneKey.Lockscreen) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (
+ (transitionState.fromScene == SceneKey.Shade &&
+ transitionState.toScene == SceneKey.QuickSettings) ||
+ (transitionState.fromScene == SceneKey.QuickSettings &&
+ transitionState.toScene == SceneKey.Shade)
+ ) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ }
+ }
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 385f061..a436f17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -87,4 +87,9 @@
fun onContentTopChanged(padding: Float) {
interactor.setContentTop(padding)
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ interactor.setScrolledToTop(scrolledToTop)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 4617ce4..3915c376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -24,13 +24,24 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
@@ -51,6 +62,7 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -69,43 +81,52 @@
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
) {
- private val statesForConstrainedNotifications =
- setOf(
- KeyguardState.AOD,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DOZING,
- KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.PRIMARY_BOUNCER
+ private val statesForConstrainedNotifications: Set<KeyguardState> =
+ setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
+
+ private val edgeToAlphaViewModel =
+ mapOf<Edge?, Flow<Float>>(
+ Edge(from = LOCKSCREEN, to = DREAMING) to
+ lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = DREAMING, to = LOCKSCREEN) to
+ dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = OCCLUDED) to
+ lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
+ Edge(from = OCCLUDED, to = LOCKSCREEN) to
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha,
)
- private val lockscreenToOccludedRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ private val lockscreenTransitionInProgress: Flow<Edge?> =
+ keyguardTransitionInteractor.transitions
+ .map { step ->
+ if (
+ (step.transitionState == STARTED || step.transitionState == RUNNING) &&
+ (step.from == LOCKSCREEN || step.to == LOCKSCREEN)
+ ) {
+ Edge(step.from, step.to)
+ } else {
+ null
+ }
+ }
.distinctUntilChanged()
- .onStart { emit(false) }
-
- private val occludedToLockscreenRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.OCCLUDED, KeyguardState.LOCKSCREEN)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
+ .onStart { emit(null) }
private val lockscreenToGlanceableHubRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ .transition(LOCKSCREEN, GLANCEABLE_HUB)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
private val glanceableHubToLockscreenRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ .transition(GLANCEABLE_HUB, LOCKSCREEN)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
@@ -141,7 +162,7 @@
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(KeyguardState.LOCKSCREEN)
+ .transitionValue(LOCKSCREEN)
.onStart { emit(0f) }
.map { it > 0 }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
@@ -242,38 +263,46 @@
initialValue = NotificationContainerBounds(),
)
+ /** As QS is expanding, fade out notifications unless in splitshade */
+ private val alphaForQsExpansion: Flow<Float> =
+ interactor.configurationBasedDimensions.flatMapLatest {
+ if (it.useSplitShade) {
+ flowOf(1f)
+ } else {
+ shadeInteractor.qsExpansion.map { 1f - it }
+ }
+ }
+
val expansionAlpha: Flow<Float> =
// Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
- // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
+ // such as when the shade resets. This can happen while the transition to/from LOCKSCREEN
// is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
// those transitions are in progress. Without this, the alpha value will produce a visible
// flicker.
- lockscreenToOccludedRunning.flatMapLatest { isLockscreenToOccludedRunning ->
- if (isLockscreenToOccludedRunning) {
- lockscreenToOccludedTransitionViewModel.lockscreenAlpha
- } else {
- occludedToLockscreenRunning.flatMapLatest { isOccludedToLockscreenRunning ->
- if (isOccludedToLockscreenRunning) {
- occludedToLockscreenTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }
- } else {
+ lockscreenTransitionInProgress
+ .flatMapLatest { edge ->
+ edgeToAlphaViewModel.getOrElse(
+ edge,
+ {
isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
combineTransform(
keyguardInteractor.keyguardAlpha,
shadeCollpaseFadeIn,
- ) { alpha, shadeCollpaseFadeIn ->
+ alphaForQsExpansion,
+ ) { alpha, shadeCollpaseFadeIn, alphaForQsExpansion ->
if (isOnLockscreenWithoutShade) {
if (!shadeCollpaseFadeIn) {
emit(alpha)
}
} else {
- emit(1f)
+ emit(alphaForQsExpansion)
}
}
}
}
- }
+ )
}
- }
+ .distinctUntilChanged()
/**
* Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 60a4606..39ca7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -179,6 +179,11 @@
}
@Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ mQSHost.addTile(tile, end);
+ }
+
+ @Override
public void remQsTile(ComponentName tile) {
mQSHost.removeTileByUser(tile);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
deleted file mode 100644
index 9f08633..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.hardware.Sensor
-import android.hardware.TriggerEvent
-import android.hardware.TriggerEventListener
-import com.android.keyguard.ActiveUnlockConfig
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.CoreStartable
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.Assert
-import com.android.systemui.util.sensors.AsyncSensorManager
-import java.io.PrintWriter
-import javax.inject.Inject
-
-/**
- * Triggers face auth on lift when the device is showing the lock screen. Only initialized
- * if face auth is supported on the device. Not to be confused with the lift to wake gesture
- * which is handled by {@link com.android.server.policy.PhoneWindowManager}.
- */
-@SysUISingleton
-class KeyguardLiftController @Inject constructor(
- private val context: Context,
- private val statusBarStateController: StatusBarStateController,
- private val asyncSensorManager: AsyncSensorManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val dumpManager: DumpManager,
- private val selectedUserInteractor: SelectedUserInteractor,
-) : Dumpable, CoreStartable {
-
- private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
- private var isListening = false
- private var bouncerVisible = false
-
- override fun start() {
- if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- init()
- }
- }
-
- private fun init() {
- dumpManager.registerDumpable(this)
- statusBarStateController.addCallback(statusBarStateListener)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- updateListeningState()
- }
-
- private val listener: TriggerEventListener = object : TriggerEventListener() {
- override fun onTrigger(event: TriggerEvent?) {
- Assert.isMainThread()
- // Not listening anymore since trigger events unregister themselves
- isListening = false
- updateListeningState()
- deviceEntryFaceAuthInteractor.onDeviceLifted()
- keyguardUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
- "KeyguardLiftController")
- }
- }
-
- private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardBouncerFullyShowingChanged(bouncer: Boolean) {
- bouncerVisible = bouncer
- updateListeningState()
- }
-
- override fun onKeyguardVisibilityChanged(visible: Boolean) {
- updateListeningState()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozingChanged(isDozing: Boolean) {
- updateListeningState()
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("KeyguardLiftController:")
- pw.println(" pickupSensor: $pickupSensor")
- pw.println(" isListening: $isListening")
- pw.println(" bouncerVisible: $bouncerVisible")
- }
-
- private fun updateListeningState() {
- if (pickupSensor == null) {
- return
- }
- val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
- !statusBarStateController.isDozing
-
- val isFaceEnabled = deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
- val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
- if (shouldListen != isListening) {
- isListening = shouldListen
-
- if (shouldListen) {
- asyncSensorManager.requestTriggerSensor(listener, pickupSensor)
- } else {
- asyncSensorManager.cancelTriggerSensor(listener, pickupSensor)
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 0051161..1c33d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -18,6 +18,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import javax.inject.Inject
@@ -40,6 +41,7 @@
constructor(
interactor: DeviceBasedSatelliteInteractor,
@Application scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
) {
private val shouldShowIcon: StateFlow<Boolean> =
interactor.areAllConnectionsOutOfService
@@ -47,7 +49,11 @@
if (!allOos) {
flowOf(false)
} else {
- interactor.isSatelliteAllowed
+ combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+ isSatelliteAllowed,
+ isAirplaneMode ->
+ isSatelliteAllowed && !isAirplaneMode
+ }
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
new file mode 100644
index 0000000..32cf86d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates device-provisioning related business logic. */
+@SysUISingleton
+class DeviceProvisioningInteractor
+@Inject
+constructor(
+ repository: DeviceProvisioningRepository,
+) {
+ /**
+ * Whether this device has been provisioned.
+ *
+ * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+ */
+ val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+ /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
+ val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..37be1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.user.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -209,18 +210,15 @@
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+ @SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
- val result = withContext(backgroundDispatcher) { manager.aliveUsers }
-
- if (result != null) {
- _userInfos.value =
- result
- // Users should be sorted by ascending creation time.
- .sortedBy { it.creationTime }
- // The guest user is always last, regardless of creation time.
- .sortedBy { it.isGuest }
- }
+ _userInfos.value =
+ withContext(backgroundDispatcher) { manager.aliveUsers }
+ // Users should be sorted by ascending creation time.
+ .sortedBy { it.creationTime }
+ // The guest user is always last, regardless of creation time.
+ .sortedBy { it.isGuest }
if (mainUserId == UserHandle.USER_NULL) {
val mainUser = withContext(backgroundDispatcher) { manager.mainUser }
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
index adae782..31a8d86 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
@@ -26,7 +26,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.flow.flowOn
/** Utility class that could give information about if animation are enabled in the system */
interface AnimationStatusRepository {
@@ -45,24 +45,26 @@
* Emits true if animations are enabled in the system, after subscribing it immediately emits
* the current state
*/
- override fun areAnimationsEnabled(): Flow<Boolean> = conflatedCallbackFlow {
- val initialValue = withContext(backgroundDispatcher) { resolver.areAnimationsEnabled() }
- trySend(initialValue)
+ override fun areAnimationsEnabled(): Flow<Boolean> =
+ conflatedCallbackFlow {
+ val initialValue = resolver.areAnimationsEnabled()
+ trySend(initialValue)
- val observer =
- object : ContentObserver(backgroundHandler) {
- override fun onChange(selfChange: Boolean) {
- val updatedValue = resolver.areAnimationsEnabled()
- trySend(updatedValue)
- }
+ val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(selfChange: Boolean) {
+ val updatedValue = resolver.areAnimationsEnabled()
+ trySend(updatedValue)
+ }
+ }
+
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notifyForDescendants= */ false,
+ observer
+ )
+
+ awaitClose { resolver.unregisterContentObserver(observer) }
}
-
- resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
- /* notifyForDescendants= */ false,
- observer
- )
-
- awaitClose { resolver.unregisterContentObserver(observer) }
- }
+ .flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 8fe57e11..d47413f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -20,21 +20,19 @@
import com.android.systemui.util.time.SystemClockImpl
import java.util.concurrent.atomic.AtomicReference
import kotlin.math.max
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -106,6 +104,14 @@
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+/** Emits a [Unit] only when the number of downstream subscribers of this flow increases. */
+fun <T> MutableSharedFlow<T>.onSubscriberAdded(): Flow<Unit> {
+ return subscriptionCount
+ .pairwise(initialValue = 0)
+ .filter { (previous, current) -> current > previous }
+ .map {}
+}
+
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
* [transform].
@@ -183,34 +189,6 @@
/**
* Returns a flow that mirrors the original flow, but delays values following emitted values for the
- * given [periodMs]. If the original flow emits more than one value during this period, only the
- * latest value is emitted.
- *
- * Example:
- * ```kotlin
- * flow {
- * emit(1) // t=0ms
- * delay(90)
- * emit(2) // t=90ms
- * delay(90)
- * emit(3) // t=180ms
- * delay(1010)
- * emit(4) // t=1190ms
- * delay(1010)
- * emit(5) // t=2200ms
- * }.throttle(1000)
- * ```
- *
- * produces the following emissions at the following times
- *
- * ```text
- * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
- * ```
- */
-fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, SystemClockImpl())
-
-/**
- * Returns a flow that mirrors the original flow, but delays values following emitted values for the
* given [periodMs] as reported by the given [clock]. If the original flow emits more than one value
* during this period, only The latest value is emitted.
*
@@ -235,70 +213,37 @@
* 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
* ```
*/
-fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelFlow {
- coroutineScope {
- var previousEmitTimeMs = 0L
- var delayJob: Job? = null
- var sendJob: Job? = null
- val outerScope = this
+fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock = SystemClockImpl()): Flow<T> =
+ channelFlow {
+ coroutineScope {
+ var previousEmitTimeMs = 0L
+ var delayJob: Job? = null
+ var sendJob: Job? = null
+ val outerScope = this
- collect {
- delayJob?.cancel()
- sendJob?.join()
- val currentTimeMs = clock.elapsedRealtime()
- val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
- val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
- if (timeUntilNextEmit > 0L) {
- // We create delayJob to allow cancellation during the delay period
- delayJob = launch {
- delay(timeUntilNextEmit)
- sendJob =
- outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
- send(it)
- previousEmitTimeMs = clock.elapsedRealtime()
- }
+ collect {
+ delayJob?.cancel()
+ sendJob?.join()
+ val currentTimeMs = clock.elapsedRealtime()
+ val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
+ val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
+ if (timeUntilNextEmit > 0L) {
+ // We create delayJob to allow cancellation during the delay period
+ delayJob = launch {
+ delay(timeUntilNextEmit)
+ sendJob =
+ outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ send(it)
+ previousEmitTimeMs = clock.elapsedRealtime()
+ }
+ }
+ } else {
+ send(it)
+ previousEmitTimeMs = currentTimeMs
}
- } else {
- send(it)
- previousEmitTimeMs = currentTimeMs
}
}
}
-}
-
-/**
- * Returns a [StateFlow] launched in the surrounding [CoroutineScope]. This [StateFlow] gets its
- * value by invoking [getValue] whenever an event is emitted from [changedSignals]. It will also
- * immediately invoke [getValue] to establish its initial value.
- */
-inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<*>,
- crossinline getValue: () -> T,
-): StateFlow<T> =
- changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
-
-inline fun <T1, T2, T3, T4, T5, T6, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
-): Flow<R> {
- return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
- ->
- @Suppress("UNCHECKED_CAST")
- transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6
- )
- }
-}
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a13d85b..cabe831 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -25,9 +25,13 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
import kotlinx.coroutines.plus
+private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
+
/** Providers for various SystemIU specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@@ -40,14 +44,13 @@
): CoroutineScope = applicationScope.plus(coroutineContext)
/**
- * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where X
- * is the number of CPU cores available.
+ * Default Coroutine dispatcher for background operations.
*
- * Because there are multiple threads at play, there is no serialization order guarantee. You
- * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
- *
- * @see Dispatchers.Default
+ * Note that this is explicitly limiting the number of threads. In the past, we used
+ * [Dispatchers.IO]. This caused >40 threads to be spawned, and a lot of thread list lock
+ * contention between then, eventually causing jank.
*/
+ @OptIn(DelicateCoroutinesApi::class)
@Provides
@SysUISingleton
@Background
@@ -55,12 +58,29 @@
"Use @Background CoroutineContext instead",
ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
)
- fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+ fun bgDispatcher(): CoroutineDispatcher {
+ return if (LIMIT_BACKGROUND_DISPATCHER_THREADS) {
+ // Why a new ThreadPool instead of just using Dispatchers.IO with
+ // CoroutineDispatcher.limitedParallelism? Because, if we were to use Dispatchers.IO, we
+ // would share those threads with other dependencies using Dispatchers.IO.
+ // Using a dedicated thread pool we have guarantees only SystemUI is able to schedule
+ // code on those.
+ newFixedThreadPoolContext(
+ nThreads = Runtime.getRuntime().availableProcessors(),
+ name = "SystemUIBg"
+ )
+ } else {
+ Dispatchers.IO
+ }
+ }
@Provides
@Background
@SysUISingleton
- fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
- return Dispatchers.IO + tracingCoroutineContext
+ fun bgCoroutineContext(
+ @Tracing tracingCoroutineContext: CoroutineContext,
+ @Background bgCoroutineDispatcher: CoroutineDispatcher,
+ ): CoroutineContext {
+ return bgCoroutineDispatcher + tracingCoroutineContext
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..e832506 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -347,10 +347,10 @@
desktopMode.addVisibleTasksListener(
new DesktopModeTaskRepository.VisibleTasksListener() {
@Override
- public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) {
+ public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
- hasFreeformTasks)
+ visibleTasksCount > 0)
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
// TODO(b/278084491): update sysui state for changes on other displays
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index b1e471a..fc34255 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.AuthenticateOptions
@@ -5,24 +21,19 @@
import android.hardware.biometrics.IBiometricContextListener.FoldState
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -30,8 +41,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@OptIn(ExperimentalCoroutinesApi::class)
@@ -41,31 +50,20 @@
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
-
- @Mock private lateinit var foldProvider: FoldStateProvider
- @Mock private lateinit var authController: AuthController
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-
- private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val deviceStateRepository = kosmos.fakeDeviceStateRepository
+ private val udfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var interactor: LogContextInteractorImpl
@Before
fun setup() {
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- udfpsOverlayInteractor =
- UdfpsOverlayInteractor(
- context,
- authController,
- selectedUserInteractor,
- testScope.backgroundScope,
- )
interactor =
LogContextInteractorImpl(
testScope.backgroundScope,
- foldProvider,
+ deviceStateRepository,
KeyguardTransitionInteractorFactory.create(
repository = keyguardTransitionRepository,
scope = testScope.backgroundScope,
@@ -189,33 +187,31 @@
@Test
fun foldStateChanges() =
testScope.runTest {
- val foldState = collectLastValue(interactor.foldState)
- runCurrent()
- val listener = foldProvider.captureListener()
+ val foldState by collectLastValue(interactor.foldState)
- listener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- assertThat(foldState()).isEqualTo(FoldState.UNKNOWN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.HALF_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.HALF_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_CLOSED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.REAR_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
+
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN)
+ assertThat(foldState).isEqualTo(FoldState.UNKNOWN)
}
@Test
fun contextSubscriberChanges() =
testScope.runTest {
- runCurrent()
- val foldListener = foldProvider.captureListener()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
var folded: Int? = null
@@ -243,8 +239,7 @@
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
assertThat(ignoreTouches).isFalse()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.LOCKSCREEN)
runCurrent()
@@ -259,7 +254,7 @@
job.cancel()
// stale updates should be ignored
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
runCurrent()
@@ -270,8 +265,3 @@
private suspend fun FakeKeyguardTransitionRepository.startTransitionTo(newState: KeyguardState) =
sendTransitionStep(TransitionStep(to = newState, transitionState = TransitionState.STARTED))
-
-private fun FoldStateProvider.captureListener() =
- withArgCaptor<FoldStateProvider.FoldUpdatesListener> {
- verify(this@captureListener).addCallback(capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 9fe40d7..8b16da2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -46,6 +47,10 @@
private lateinit var underTest: KeyguardBlueprintInteractor
private lateinit var testScope: TestScope
+ val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
@@ -54,6 +59,9 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope(StandardTestDispatcher())
whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
+ whenever(keyguardBlueprintRepository.refreshBluePrint).thenReturn(refreshBluePrint)
+ whenever(keyguardBlueprintRepository.refreshBlueprintTransition)
+ .thenReturn(refreshBlueprintTransition)
underTest =
KeyguardBlueprintInteractor(
@@ -105,4 +113,11 @@
underTest.transitionToBlueprint("abc")
verify(keyguardBlueprintRepository).applyBlueprint("abc")
}
+
+ @Test
+ fun testRefreshBlueprintWithTransition() {
+ underTest.refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ verify(keyguardBlueprintRepository)
+ .refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index e93ad0be3..2812718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1895,6 +1895,37 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun glanceableHubToDreaming() =
+ testScope.runTest {
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN the device begins to dream
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DREAMING should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index acb6ff0..2da74b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -165,7 +165,7 @@
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
@@ -178,7 +178,7 @@
setSplitShade(false)
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index 75994da..ad2ae8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -19,7 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -65,7 +65,7 @@
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val shadeRepository = kosmos.shadeRepository
private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
- private val primaryBouncerInteractor = kosmos.primaryBouncerInteractor
+ private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
private val underTest = kosmos.bouncerToGoneFlows
@Before
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 ba7927d..239bf65 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
@@ -532,6 +532,58 @@
}
@Test
+ fun testCommunalLocation_showsOverLockscreen() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // UMO goes to communal even over the lock screen.
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testCommunalLocation_showsUntilQsExpands() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ // Start opening the shade.
+ mediaHierarchyManager.qsExpansion = 0.1f
+ runCurrent()
+
+ // UMO goes to the shade instead.
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QS),
+ 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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index a92111e..da8d29c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -246,8 +246,9 @@
@Test
- public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+ public void testShouldUseHorizontalLayout_falseForSplitShade() {
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
@@ -270,12 +271,13 @@
}
@Test
- public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLandscape_true() {
when(mMediaHost.getVisible()).thenReturn(true);
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
- // When device is rotated to landscape
+ // When device is rotated to landscape and is long
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
// Then the layout changes
@@ -292,6 +294,29 @@
}
@Test
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLongDevices_true() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape and is long
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run();
+
+ // When device changes to not-long
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_NO;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes back
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
+ }
+
+ @Test
public void testRefreshAllTilesDoesntRefreshListeningTiles() {
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
@@ -310,6 +335,7 @@
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b3e386e..cc27cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -189,7 +189,6 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -411,7 +410,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
mFakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 461db8e..7f4508a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -99,7 +99,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -260,7 +259,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mock(DozeParameters.class),
keyguardRepository,
@@ -452,11 +451,11 @@
}
@Test
- public void setCommunalShowing_userTimeout() {
+ public void setCommunalVisible_userTimeout() {
setKeyguardShowing();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ mNotificationShadeWindowController.onCommunalVisibleChanged(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat(mLayoutParameters.getValue().userActivityTimeout)
.isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 3e0a647..7bd9d92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -103,7 +103,6 @@
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -205,9 +204,7 @@
mStatusBarStateController = mKosmos.getStatusBarStateController();
mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
- FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
- deviceProvisioningRepository.setDeviceProvisioned(true);
+ mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
@@ -294,7 +291,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
mDisableFlagsRepository,
mDozeParameters,
mKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 190ee81..460892a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -49,7 +49,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 260bef8..5da3a56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -32,11 +33,14 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.test.filters.SmallTest;
@@ -365,14 +369,50 @@
}
@Test
- public void testAddQsTile() {
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOff() {
ComponentName c = new ComponentName("testpkg", "testcls");
+
mCommandQueue.addQsTile(c);
waitForIdleSync();
+
verify(mCallbacks).addQsTile(eq(c));
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileToFrontOrEnd_withA11yQsShortcutFlagOff_doNothing() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verifyZeroInteractions(mCallbacks);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTile(c);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileAtTheEnd_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(true));
+ }
+
+ @Test
public void testRemoveQsTile() {
ComponentName c = new ComponentName("testpkg", "testcls");
mCommandQueue.remQsTile(c);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8cb064d..5450537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -56,8 +56,8 @@
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
@@ -192,7 +192,7 @@
shadeInteractor =
ShadeInteractorImpl(
testScope.backgroundScope,
- FakeDeviceProvisioningRepository(),
+ kosmos.deviceProvisioningInteractor,
FakeDisableFlagsRepository(),
mock(),
keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 912c27d..ea4ae17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -26,6 +26,7 @@
import android.app.ActivityManager;
import android.app.StatusBarManager;
+import android.content.ComponentName;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -190,4 +191,31 @@
HapticFeedbackConstants.GESTURE_START
);
}
+
+ @Test
+ public void addQsTile_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTile(c);
+
+ verify(mQSHost).addTile(c);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheEnd_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, true);
+
+ verify(mQSHost).addTile(c, true);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheFront_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, false);
+
+ verify(mQSHost).addTile(c, false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 21c038a..f53fc46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -36,6 +37,7 @@
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
private lateinit var underTest: DeviceBasedSatelliteViewModel
private lateinit var interactor: DeviceBasedSatelliteInteractor
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private val repo = FakeDeviceBasedSatelliteRepository()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -45,6 +47,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
interactor =
DeviceBasedSatelliteInteractor(
@@ -57,6 +60,7 @@
DeviceBasedSatelliteViewModel(
interactor,
testScope.backgroundScope,
+ airplaneModeRepository,
)
}
@@ -72,6 +76,9 @@
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we should not be showing it
assertThat(latest).isNull()
}
@@ -88,11 +95,33 @@
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = true
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we have service
assertThat(latest).isNull()
}
@Test
+ fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // GIVEN apm is enabled
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN icon is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_satelliteIsOff() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index abfff34..0669cb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -31,6 +31,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
@@ -46,6 +47,7 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7eba3f0..9ea4142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -494,7 +494,7 @@
mShadeInteractor =
new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
keyguardRepository,
@@ -2098,6 +2098,26 @@
}
@Test
+ public void testShowOrHideAppBubble_addsFromOverflow() {
+ String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+
+ // Collapse the stack so we don't need to wait for the dismiss animation in the test
+ mBubbleController.collapseStack();
+
+ // Dismiss the app bubble so it's in the overflow
+ mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull();
+
+ // Calling this while collapsed will re-add and expand the app bubble
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ }
+
+ @Test
public void testCreateBubbleFromOngoingNotification() {
NotificationEntry notif = new NotificationEntryBuilder()
.setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
@@ -2217,7 +2237,8 @@
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 500, 1000);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(),
+ new Rect(500, 1000, 600, 1100));
assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
new file mode 100644
index 0000000..8901314
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content
+
+import android.content.pm.PackageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
index 06b6cda6..244ef8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorKosmos.kt
@@ -16,7 +16,40 @@
package com.android.systemui.bouncer.domain.interactor
+import android.content.applicationContext
+import com.android.keyguard.keyguardSecurityModel
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.trustRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.mockito.mock
-var Kosmos.primaryBouncerInteractor by Kosmos.Fixture { mock<PrimaryBouncerInteractor>() }
+var Kosmos.mockPrimaryBouncerInteractor by Kosmos.Fixture { mock<PrimaryBouncerInteractor>() }
+var Kosmos.primaryBouncerInteractor by
+ Kosmos.Fixture {
+ PrimaryBouncerInteractor(
+ repository = keyguardBouncerRepository,
+ primaryBouncerView = mock<BouncerView>(),
+ mainHandler = mockExecutorHandler(executor = fakeExecutor),
+ keyguardStateController = mock<KeyguardStateControllerImpl>(),
+ keyguardSecurityModel = keyguardSecurityModel,
+ primaryBouncerCallbackInteractor = mock<PrimaryBouncerCallbackInteractor>(),
+ falsingCollector = falsingCollector,
+ dismissCallbackRegistry = mock<DismissCallbackRegistry>(),
+ context = applicationContext,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ trustRepository = trustRepository,
+ applicationScope = applicationCoroutineScope,
+ selectedUserInteractor = selectedUserInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index d91c597..99dfe94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,11 +21,13 @@
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.systemClock
@@ -36,8 +38,10 @@
applicationScope = testScope.backgroundScope,
mainDispatcher = testDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = sceneContainerFlags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index bc7e7af..fab64e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -36,6 +36,14 @@
}
}
+ override fun deleteWidget(widgetId: Int) {
+ if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
+ return
+ }
+
+ _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
+ }
+
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
index 002862e..a231212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.panels
import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -68,3 +69,6 @@
}
}
}
+
+val Kosmos.selectedComponentRepository by
+ Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 0b1fb40..5575b05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -23,7 +23,7 @@
import com.android.keyguard.trustManager
import com.android.systemui.biometrics.data.repository.facePropertyRepository
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
@@ -46,7 +46,7 @@
applicationScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
repository = deviceEntryFaceAuthRepository,
- primaryBouncerInteractor = { primaryBouncerInteractor },
+ primaryBouncerInteractor = { mockPrimaryBouncerInteractor },
alternateBouncerInteractor = alternateBouncerInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
faceAuthenticationLogger = faceAuthLogger,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt
new file mode 100644
index 0000000..2fead91
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.ui.binder
+
+import android.content.packageManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.util.sensors.asyncSensorManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.liftToRunFaceAuthBinder by
+ Kosmos.Fixture {
+ LiftToRunFaceAuthBinder(
+ scope = applicationCoroutineScope,
+ packageManager = packageManager,
+ asyncSensorManager = asyncSensorManager,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ keyguardInteractor = keyguardInteractor,
+ primaryBouncerInteractor = primaryBouncerInteractor,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ powerInteractor = powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt
new file mode 100644
index 0000000..5855060
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceStateRepository: DeviceStateRepository by
+ Kosmos.Fixture { fakeDeviceStateRepository }
+val Kosmos.fakeDeviceStateRepository: FakeDeviceStateRepository by
+ Kosmos.Fixture { FakeDeviceStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
new file mode 100644
index 0000000..8e4461d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class FakeInputMethodRepository : InputMethodRepository {
+
+ private var usersToEnabledInputMethods: MutableMap<Int, Flow<InputMethodModel>> = mutableMapOf()
+
+ var selectedInputMethodSubtypes = listOf<InputMethodModel.Subtype>()
+
+ /**
+ * The display ID on which the input method picker dialog was shown, or `null` if the dialog was
+ * not shown.
+ */
+ var inputMethodPickerShownDisplayId: Int? = null
+
+ fun setEnabledInputMethods(userId: Int, vararg enabledInputMethods: InputMethodModel) {
+ usersToEnabledInputMethods[userId] = enabledInputMethods.asFlow()
+ }
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel> {
+ return usersToEnabledInputMethods[userId] ?: flowOf()
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
+ selectedInputMethodSubtypes
+
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ inputMethodPickerShownDisplayId = displayId
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt
new file mode 100644
index 0000000..b71b9d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.inputMethodRepository: InputMethodRepository by
+ Kosmos.Fixture { fakeInputMethodRepository }
+val Kosmos.fakeInputMethodRepository by Kosmos.Fixture { FakeInputMethodRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt
new file mode 100644
index 0000000..da77575
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.domain.interactor
+
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inputMethodInteractor by
+ Kosmos.Fixture {
+ InputMethodInteractor(
+ repository = inputMethodRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
index c71c1c3..ffa4133 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
@@ -18,7 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
@@ -31,7 +31,7 @@
val Kosmos.bouncerToGoneFlows by Fixture {
BouncerToGoneFlows(
statusBarStateController = sysuiStatusBarStateController,
- primaryBouncerInteractor = primaryBouncerInteractor,
+ primaryBouncerInteractor = mockPrimaryBouncerInteractor,
keyguardDismissActionInteractor = mock(),
featureFlags = featureFlagsClassic,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
index ab28d0d6..4ecff73 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
@@ -30,7 +30,7 @@
val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture {
PrimaryBouncerToGoneTransitionViewModel(
statusBarStateController = sysuiStatusBarStateController,
- primaryBouncerInteractor = primaryBouncerInteractor,
+ primaryBouncerInteractor = mockPrimaryBouncerInteractor,
keyguardDismissActionInteractor = mock(),
featureFlags = featureFlagsClassic,
bouncerToGoneFlows = bouncerToGoneFlows,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 083de10..b9a3d38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -43,6 +43,8 @@
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,6 +82,8 @@
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+ val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
+ val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 0000000..9410ce6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index afd37b3..2bd76be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
@@ -63,7 +63,7 @@
Kosmos.Fixture {
ShadeInteractorImpl(
scope = applicationCoroutineScope,
- deviceProvisioningRepository = deviceProvisioningRepository,
+ deviceProvisioningInteractor = deviceProvisioningInteractor,
disableFlagsRepository = disableFlagsRepository,
dozeParams = dozeParameters,
keyguardRepository = fakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
index f2f3a5a..d79633a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -25,5 +26,6 @@
NotificationStackAppearanceViewModel(
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7c398cd..549a775 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -20,7 +20,9 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
@@ -40,6 +42,8 @@
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
new file mode 100644
index 0000000..84bd3e8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+
+val Kosmos.deviceProvisioningInteractor by Fixture {
+ DeviceProvisioningInteractor(
+ repository = deviceProvisioningRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/AsyncSensorManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/AsyncSensorManagerKosmos.kt
new file mode 100644
index 0000000..117ae8c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/AsyncSensorManagerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.sensors
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.asyncSensorManager by Kosmos.Fixture { mock<AsyncSensorManager>() }
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
new file mode 100644
index 0000000..36d398c
--- /dev/null
+++ b/ravenwood/bulk_enable.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Tool to bulk-enable tests that are now passing on Ravenwood.
+
+Currently only offers to include classes which are fully passing; ignores
+classes that have partial success.
+
+Typical usage:
+$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
+SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
+
+STATE_PASSED = "PASSED"
+STATE_FAILURE = "FAILURE"
+STATE_ASSUMPTION_FAILURE = "ASSUMPTION_FAILURE"
+STATE_CANDIDATE = "CANDIDATE"
+
+stats_total = collections.defaultdict(int)
+stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
+stats_method = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+ for line in f.readlines():
+ result = re_result.search(line)
+ if result:
+ clazz, method, state, msg = result.groups()
+ if state == STATE_FAILURE and "actually passed under Ravenwood" in msg:
+ state = STATE_CANDIDATE
+ stats_total[state] += 1
+ stats_class[clazz][state] += 1
+ stats_method[(clazz, method)] = state
+
+# Find classes who are fully "candidates" (would be entirely green if enabled)
+num_enabled = 0
+for clazz in stats_class.keys():
+ stats = stats_class[clazz]
+ if STATE_CANDIDATE in stats and len(stats) == 1:
+ num_enabled += stats[STATE_CANDIDATE]
+ print("Enabling fully-passing class", clazz)
+ clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+ for root, dirs, files in os.walk("."):
+ for f in files:
+ if clazz_match.match(f):
+ path = os.path.join(root, f)
+ subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+
+print("Overall stats", stats_total)
+print("Candidates actually enabled", num_enabled)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index a797b1d..3670459 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -20,9 +20,14 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.os.RuntimeInit;
+
+import org.junit.runner.Description;
+
import java.io.PrintStream;
import java.util.Map;
import java.util.concurrent.Executors;
@@ -50,6 +55,8 @@
}
public static void init(RavenwoodRule rule) {
+ RuntimeInit.redirectLogStreams();
+
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
android.os.SystemProperties.init$ravenwood(
@@ -92,6 +99,13 @@
android.os.Process.reset$ravenwood();
}
+ public static void logTestRunner(String label, Description description) {
+ // This message string carefully matches the exact format emitted by on-device tests, to
+ // aid developers in debugging raw text logs
+ Log.e("TestRunner", label + ": " + description.getMethodName()
+ + "(" + description.getTestClass().getName() + ")");
+ }
+
private static void dumpStacks() {
final PrintStream out = System.err;
out.println("-----BEGIN ALL THREAD STACKS-----");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 1e7cbf6..0285b38 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -280,9 +280,14 @@
public void evaluate() throws Throwable {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
+ RavenwoodRuleImpl.logTestRunner("finished", description);
+ } catch (Throwable t) {
+ RavenwoodRuleImpl.logTestRunner("failed", description);
+ throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
@@ -300,6 +305,8 @@
@Override
public void evaluate() throws Throwable {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
+
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
@@ -309,6 +316,7 @@
Assume.assumeTrue(shouldEnableOnRavenwood(description));
throw t;
} finally {
+ RavenwoodRuleImpl.logTestRunner("finished", description);
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index d0c2e18..7d172f2 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,6 +16,8 @@
package android.platform.test.ravenwood;
+import org.junit.runner.Description;
+
public class RavenwoodRuleImpl {
public static boolean isOnRavenwood() {
return false;
@@ -28,4 +30,8 @@
public static void reset(RavenwoodRule rule) {
// No-op when running on a real device
}
+
+ public static void logTestRunner(String label, Description description) {
+ // No-op when running on a real device
+ }
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b775f9a..e49b64e 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -5,6 +5,7 @@
com.android.internal.logging.MetricsLogger
com.android.internal.logging.testing.FakeMetricsLogger
com.android.internal.logging.testing.UiEventLoggerFake
+com.android.internal.os.AndroidPrintStream
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -16,6 +17,7 @@
com.android.internal.os.PowerProfile
com.android.internal.os.PowerStats
com.android.internal.os.PowerStats$Descriptor
+com.android.internal.os.RuntimeInit
com.android.internal.power.ModemPowerProfile
android.util.AtomicFile
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index d08a97e..2a85eb6 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -21,7 +21,6 @@
libs: ["services.core"],
static_libs: [
"app-compat-annotations",
- "backup_flags_lib",
],
lint: {
baseline_filename: "lint-baseline.xml",
@@ -33,8 +32,3 @@
package: "com.android.server.backup",
srcs: ["flags.aconfig"],
}
-
-java_aconfig_library {
- name: "backup_flags_lib",
- aconfig_declarations: "backup_flags",
-}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 1416c88..6a63b3a 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -24,4 +24,12 @@
description: "Enables the write buffer to pipes to be of maximum size."
bug: "265976737"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_clear_pipe_after_restore_file"
+ namespace: "onboarding"
+ description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
+ bug: "320633449"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 0559708..d85dd87 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -732,6 +732,7 @@
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
nextState = UnifiedRestoreState.FINAL;
} finally {
executeNextState(nextState);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8e35b74..89896c3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,7 +211,6 @@
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
- "backup_flags_lib",
"policy_flags_lib",
"net_flags_lib",
"stats_flags_lib",
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index a1e6b58f..81c9ee7 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -81,7 +81,7 @@
* Returns whether an intent matches the IntentFilter with a pre-resolved type.
*/
public static boolean intentMatchesFilter(
- IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) {
+ IntentFilter filter, Intent intent, String resolvedType) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -97,10 +97,6 @@
int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
intent.getData(), intent.getCategories(), TAG);
- if (match >= 0 && defaultOnly && !filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
- match = IntentFilter.NO_MATCH_CATEGORY;
- }
-
if (match >= 0) {
if (debug) {
Slog.v(TAG, "Filter matched! match=0x" + Integer.toHexString(match));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 374a17a..a5531ae 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2101,7 +2101,8 @@
// Start watching app ops after we and the package manager are up and running.
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
- @Override public void opChanged(int op, int uid, String packageName) {
+ @Override public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
if (getAppOpsManager().checkOpNoThrow(op, uid, packageName)
!= AppOpsManager.MODE_ALLOWED) {
@@ -2115,7 +2116,7 @@
mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, @AttributionFlags int attributionFlags,
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
cameraActiveChanged(uid, active);
}
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 18a9153..c641b35 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -393,7 +393,7 @@
private class MyAppOpsCallback extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName)
.sendToTarget();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7aafda5..1207616 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -162,6 +162,7 @@
"nfc",
"pdf_viewer",
"pixel_audio_android",
+ "pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_system_sw_video",
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 0916967..9535db8 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -220,7 +220,7 @@
}
for (int i = 0; i < listenersCopy.size(); i++) {
- listenersCopy.get(i).onUidModeChanged(uid, op, mode);
+ listenersCopy.get(i).onUidModeChanged(uid, op, mode, persistentDeviceId);
}
return true;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index f056f6b..8b4ea67 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -176,8 +176,9 @@
* @param uid The UID whose appop mode was changed.
* @param code The op code that was changed.
* @param mode The new mode.
+ * @param persistentDeviceId the device whose mode was changed
*/
- void onUidModeChanged(int uid, int code, int mode);
+ void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId);
/**
* Invoked when a package's appop mode is changed.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2ed217a..5c95d43 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -166,6 +166,7 @@
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.PackageList;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
@@ -275,6 +276,10 @@
= new AppOpsManagerInternalImpl();
@Nullable private final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
+ @Nullable private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+
+ /** Map of virtual device id -> persistent device id. */
+ private final SparseArray<String> mKnownDeviceIds = new SparseArray<>();
private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -595,45 +600,71 @@
final UidState uidState;
final @NonNull String packageName;
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+ /**
+ * Map to retrieve {@link AttributedOp} for a particular device and attribution tag.
+ *
+ * ArrayMap<Persistent Device Id, ArrayMap<Attribution Tag, AttributedOp>>
+ */
+ final ArrayMap<String, ArrayMap<String, AttributedOp>> mDeviceAttributedOps =
+ new ArrayMap<String, ArrayMap<String, AttributedOp>>(1);
Op(UidState uidState, String packageName, int op, int uid) {
this.op = op;
this.uid = uid;
this.uidState = uidState;
this.packageName = packageName;
+ // We keep an invariant that the persistent device will always have an entry in
+ // mDeviceAttributedOps.
+ mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<String, AttributedOp>());
}
void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
+ for (int deviceIndex = mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0; tagIndex--) {
+ if (!attributedOps.valueAt(tagIndex).hasAnyTime()) {
+ attributedOps.removeAt(tagIndex);
+ }
+ }
+ if (!Objects.equals(PERSISTENT_DEVICE_ID_DEFAULT,
+ mDeviceAttributedOps.keyAt(deviceIndex)) && attributedOps.isEmpty()) {
+ mDeviceAttributedOps.removeAt(deviceIndex);
}
}
}
private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
+ @Nullable String attributionTag, String persistentDeviceId) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ persistentDeviceId);
+ if (attributedOps == null) {
+ attributedOps = new ArrayMap<>();
+ mDeviceAttributedOps.put(persistentDeviceId, attributedOps);
+ }
+ AttributedOp attributedOp = attributedOps.get(attributionTag);
- attributedOp = mAttributions.get(attributionTag);
if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
- mAttributions.put(attributionTag, attributedOp);
+ attributedOp = new AttributedOp(AppOpsService.this, attributionTag,
+ persistentDeviceId, parent);
+ attributedOps.put(attributionTag, attributedOp);
}
return attributedOp;
}
@NonNull OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ new ArrayMap<>(attributedOps.size());
+ for (int i = 0; i < attributedOps.size(); i++) {
+ attributionEntries.put(attributedOps.keyAt(i),
+ attributedOps.valueAt(i).createAttributedOpEntryLocked());
}
return new OpEntry(
@@ -644,17 +675,15 @@
}
@NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
+ if (attributedOps.get(attributionTag) != null) {
+ attributionEntries.put(attributionTag,
+ attributedOps.get(attributionTag).createAttributedOpEntryLocked());
}
-
return new OpEntry(
op,
mAppOpsCheckingService.getPackageMode(
@@ -663,13 +692,15 @@
}
boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
+ for (int deviceIndex = 0; deviceIndex < mDeviceAttributedOps.size(); deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ if (attributedOps.valueAt(tagIndex).isRunning()) {
+ return true;
+ }
}
}
-
return false;
}
}
@@ -738,7 +769,15 @@
@Override
public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
+ throw new IllegalStateException(
+ "unimplemented onOpModeChanged method called for op: " + op + " uid: " + uid
+ + " packageName: " + packageName);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ mCallback.opChanged(op, uid, packageName, persistentDeviceId);
}
}
@@ -910,6 +949,7 @@
public AppOpsService(File recentAccessesFile, File storageFile, Handler handler,
Context context) {
mContext = context;
+ mKnownDeviceIds.put(Context.DEVICE_ID_DEFAULT, PERSISTENT_DEVICE_ID_DEFAULT);
for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
int switchCode = AppOpsManager.opToSwitch(switchedCode);
@@ -927,10 +967,11 @@
mAppOpsCheckingService.addAppOpsModeChangedListener(
new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() {
@Override
- public void onUidModeChanged(int uid, int code, int mode) {
+ public void onUidModeChanged(int uid, int code, int mode,
+ String persistentDeviceId) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
- code, uid, false));
+ code, uid, false, persistentDeviceId));
}
@Override
@@ -941,8 +982,9 @@
packageName, code, mode, userId));
}
});
+ // Only notify default device as other devices are unaffected by restriction changes.
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- code -> notifyWatchersOfChange(code, UID_ANY));
+ code -> notifyWatchersOnDefaultDevice(code, UID_ANY));
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mStorageFile = new AtomicFile(storageFile, "appops_legacy");
@@ -1043,25 +1085,28 @@
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
+ for (int deviceIndex = op.mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0;
+ tagIndex--) {
+ String tag = attributedOps.keyAt(tagIndex);
+ if (attributionTags.contains(tag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
+ String newAttributionTag = dstAttributionTags.get(tag);
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag,
+ op.mDeviceAttributedOps.keyAt(deviceIndex));
+ newAttributedOp.add(attributedOps.get(tag));
+ attributedOps.remove(tag);
+
+ scheduleFastWriteLocked();
}
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
-
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
-
- scheduleFastWriteLocked();
}
}
}
@@ -1070,6 +1115,8 @@
};
public void systemReady() {
+ mVirtualDeviceManagerInternal = LocalServices.getService(
+ VirtualDeviceManagerInternal.class);
mAppOpsCheckingService.systemReady();
initializeUidStates();
@@ -1144,7 +1191,17 @@
final String changedPkg = changedPkgs[i];
// We trust packagemanager to insert matching uid and packageNames in the
// extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg,
+ device);
+ }
}
}
}
@@ -1295,17 +1352,19 @@
final int numOps = removedOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = removedOps.valueAt(opNum);
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
}
}
}
@@ -1380,7 +1439,7 @@
== AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true));
+ this, code, uidState.uid, true, PERSISTENT_DEVICE_ID_DEFAULT));
} else if (!uidState.pkgOps.isEmpty()) {
final ArraySet<OnOpModeChangedListener> listenerSet =
mOpModeWatchers.get(code);
@@ -1405,7 +1464,8 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
+ uidState.pkgOps.keyAt(pkgi),
+ PERSISTENT_DEVICE_ID_DEFAULT));
}
}
}
@@ -1422,14 +1482,15 @@
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size();
+ tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
+ attributedOp.onUidStateChanged(state);
+ }
}
}
}
@@ -1774,7 +1835,7 @@
private void pruneOpLocked(Op op, int uid, String packageName) {
op.removeAttributionsWithNoTime();
- if (op.mAttributions.isEmpty()) {
+ if (op.mDeviceAttributedOps.isEmpty()) {
Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
if (ops != null) {
ops.remove(op.op);
@@ -1870,8 +1931,10 @@
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) {
return;
}
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime
+ // permissions is deprecated.
if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
@@ -1884,8 +1947,10 @@
* @param code The op that changed
* @param uid The uid the op was changed for
* @param onlyForeground Only notify watchers that watch for foreground changes
+ * @param persistentDeviceId device the op was changed for
*/
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground) {
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ String persistentDeviceId) {
String[] uidPackageNames = getPackagesForUid(uid);
ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
synchronized (this) {
@@ -1951,17 +2016,17 @@
final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, (String) null));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, (String) null, persistentDeviceId));
} else {
final int reportedPackageCount = reportedPackageNames.size();
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, reportedPackageName));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, reportedPackageName, persistentDeviceId));
}
}
}
@@ -1999,13 +2064,17 @@
}
scheduleFastWriteLocked();
if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware,
+ // not package modes.
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
if (repCbs != null && uid != -1) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware, not
+ // package modes.
mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
- repCbs, code, uid, packageName));
+ repCbs, code, uid, packageName, PERSISTENT_DEVICE_ID_DEFAULT));
}
}
@@ -2161,15 +2230,15 @@
}
private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
for (int i = 0; i < callbacks.size(); i++) {
final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
+ notifyOpChanged(callback, code, uid, packageName, persistentDeviceId);
}
}
private void notifyOpChanged(OnOpModeChangedListener onModeChangedListener, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
Objects.requireNonNull(onModeChangedListener);
if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
@@ -2197,7 +2266,8 @@
onModeChangedListener.getCallingUid())) {
continue;
}
- onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+ onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName,
+ persistentDeviceId);
} catch (RemoteException e) {
/* ignore */
} finally {
@@ -2289,7 +2359,8 @@
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Check non default modes for all devices.
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for
+ // runtime permissions is deprecated.
SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -2301,7 +2372,6 @@
int previousMode = opModes.valueAt(j);
int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
AppOpsManager.opToDefaultMode(code);
- // TODO(b/299330771): Set mode for all necessary devices.
mAppOpsCheckingService.setUidMode(
uidState.uid,
PERSISTENT_DEVICE_ID_DEFAULT,
@@ -2375,7 +2445,7 @@
allChanges = addChange(allChanges, curOp.op, uid, packageName,
previousMode);
curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
+ if (curOp.mDeviceAttributedOps.isEmpty()) {
pkgOps.removeAt(j);
}
}
@@ -2399,9 +2469,18 @@
ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg, device));
+ }
}
}
}
@@ -2595,6 +2674,12 @@
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean raw) {
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return AppOpsManager.MODE_IGNORED;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return AppOpsManager.opToDefaultMode(code);
}
@@ -2603,7 +2688,8 @@
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId, raw);
}
/**
@@ -2617,7 +2703,7 @@
* @return The mode of the op
*/
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, null);
@@ -2630,19 +2716,19 @@
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
- // TODO(b/299330771): Check mode for the relevant device.
if (uidState != null
&& mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code)
+ uidState.uid, getPersistentId(virtualDeviceId), code)
!= AppOpsManager.opToDefaultMode(code)) {
final int rawMode =
mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code);
+ uidState.uid, getPersistentId(virtualDeviceId), code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
@@ -2683,8 +2769,9 @@
mAudioRestrictionManager.setZenModeAudioRestriction(
code, usage, uid, mode, exceptionPackages);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
}
@@ -2694,11 +2781,12 @@
mAudioRestrictionManager.setCameraAudioRestriction(mode);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@@ -2761,11 +2849,18 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "noteProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))
|| !isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -2792,8 +2887,9 @@
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
- resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+ resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ "proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
@@ -2810,8 +2906,9 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
- proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
@Override
@@ -2838,6 +2935,13 @@
boolean shouldCollectMessage) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT) {
@@ -2854,15 +2958,16 @@
packageName);
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, Process.INVALID_UID, null, null,
+ AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -2891,8 +2996,8 @@
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
@@ -2911,7 +3016,8 @@
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ code + " startTime of in progress event="
@@ -2920,33 +3026,33 @@
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, false)) {
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, uidMode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2969,8 +3075,8 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, mode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2986,11 +3092,10 @@
: "." + attributionTag) + " flags: "
+ AppOpsManager.flagsToString(flags));
}
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
+ uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3314,6 +3419,13 @@
int attributionChainId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "startOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
@@ -3349,9 +3461,9 @@
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3390,11 +3502,18 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -3435,7 +3554,8 @@
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+ proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
@@ -3445,8 +3565,9 @@
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags,
+ startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
@@ -3454,9 +3575,9 @@
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
- proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3464,11 +3585,11 @@
}
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId) {
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
+ @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3492,8 +3613,8 @@
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED, startType,
+ attributionFlags, attributionChainId);
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
@@ -3501,23 +3622,23 @@
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3527,7 +3648,8 @@
}
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, uidMode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3547,7 +3669,8 @@
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, mode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
@@ -3557,19 +3680,19 @@
try {
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId,
+ flags, isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
attributionChainId);
}
@@ -3590,7 +3713,7 @@
* the proxied app can successfully start the operation.
*/
private SyncNotedAppOp startOperationDryRun(int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
String proxyPackageName, @OpFlags int flags,
boolean startIfModeDefault) {
PackageVerificationResult pvr;
@@ -3624,21 +3747,20 @@
}
final Op op = getOpLocked(ops, code, uid, true);
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default mode per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3697,6 +3819,11 @@
String attributionTag, int virtualDeviceId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "finishOperationImpl was a no-op as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return;
}
@@ -3706,7 +3833,8 @@
return;
}
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId);
}
/** @deprecated Use {@link #finishProxyOperationWithState} instead. */
@@ -3731,6 +3859,7 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
@@ -3739,6 +3868,11 @@
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "finishProxyOperationImpl was a no-op as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return null;
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return null;
@@ -3752,7 +3886,7 @@
if (!skipProxyOperation) {
finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, proxyVirtualDeviceId);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3762,13 +3896,13 @@
}
finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag);
+ proxiedAttributionTag, proxyVirtualDeviceId);
return null;
}
private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
+ String attributionTag, int virtualDeviceId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag);
@@ -3788,7 +3922,9 @@
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
return;
}
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId),
+ new ArrayMap<>()).get(attributionTag);
if (attributedOp == null) {
Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
@@ -3805,8 +3941,8 @@
}
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
- String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<ActiveCallback> dispatchedCallbacks = null;
final int callbackListCount = mActiveWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3827,13 +3963,14 @@
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, active, attributionFlags, attributionChainId));
}
private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
@@ -3847,7 +3984,7 @@
continue;
}
callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
+ virtualDeviceId, active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3858,7 +3995,7 @@
}
void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result,
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
@@ -3885,13 +4022,14 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, virtualDeviceId,
+ flags, result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -3902,8 +4040,9 @@
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result, startedType, attributionFlags,
+ attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3914,7 +4053,7 @@
}
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result) {
ArraySet<NotedCallback> dispatchedCallbacks = null;
final int callbackListCount = mNotedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3935,13 +4074,13 @@
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result));
}
private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result) {
// There are features watching for checks in our process. The callbacks in
// these features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
@@ -3953,8 +4092,8 @@
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result);
} catch (RemoteException e) {
/* do nothing */
}
@@ -4028,6 +4167,22 @@
watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
}
+ private boolean isValidVirtualDeviceId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal.isValidVirtualDeviceId(virtualDeviceId)) {
+ mKnownDeviceIds.put(virtualDeviceId,
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId));
+ return true;
+ }
+
+ return false;
+ }
+
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -4488,7 +4643,12 @@
}
private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass,
+ boolean isCheckOp) {
+ // Restrictions only apply to the default device.
+ if (virtualDeviceId != Context.DEVICE_ID_DEFAULT) {
+ return false;
+ }
int restrictionSetCount = mOpGlobalRestrictions.size();
for (int i = 0; i < restrictionSetCount; i++) {
@@ -4662,7 +4822,10 @@
private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@Nullable String attribution)
throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final AttributedOp attributedOp =
+ parent.getOrCreateAttribution(parent, attribution, PERSISTENT_DEVICE_ID_DEFAULT);
final long key = parser.getAttributeLong(null, "n");
final int uidState = extractUidStateFromKey(key);
@@ -5369,18 +5532,23 @@
private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
+ // TODO(b/299330771): Dump data for all devices.
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+
+ final int numAttributions = defaultDeviceAttributedOps.size();
for (int i = 0; i < numAttributions; i++) {
if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
+ defaultDeviceAttributedOps.keyAt(i), filterAttributionTag)) {
continue;
}
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
+ pw.print(prefix + defaultDeviceAttributedOps.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, defaultDeviceAttributedOps.keyAt(i), now, sdf,
+ date, prefix + " ");
pw.print(prefix + "]\n");
}
+
}
private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
@@ -5462,8 +5630,11 @@
pw.println();
}
}
+ // TODO(b/299330771): Dump running starts for all devices.
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<>()).get(attributionTag);
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
if (attributedOp.isRunning()) {
long earliestElapsedTime = Long.MAX_VALUE;
long maxNumStarts = 0;
@@ -5828,7 +5999,7 @@
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Get modes for all devices.
+ // TODO(b/299330771): Dump modes for all devices.
final SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -6043,11 +6214,13 @@
if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
userHandle)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, this, code, restricted,
- userHandle));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice, this, code,
+ restricted, userHandle));
}
if (restrictionState.isDefault()) {
@@ -6057,7 +6230,8 @@
}
}
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ private void updateStartedOpModeForUserForDefaultDevice(int code, boolean restricted,
+ int userId) {
synchronized (AppOpsService.this) {
int numUids = mUidStates.size();
for (int uidNum = 0; uidNum < numUids; uidNum++) {
@@ -6065,12 +6239,13 @@
if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
continue;
}
- updateStartedOpModeForUidLocked(code, restricted, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, restricted, uid);
}
}
}
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ private void updateStartedOpModeForUidForDefaultDeviceLocked(int code, boolean restricted,
+ int uid) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
@@ -6089,9 +6264,11 @@
if (mode != MODE_ALLOWED && mode != MODE_FOREGROUND) {
continue;
}
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+ for (int tagIndex = 0; tagIndex < defaultDeviceAttributedOps.size();
+ tagIndex++) {
+ AttributedOp attrOp = defaultDeviceAttributedOps.valueAt(tagIndex);
if (restricted && attrOp.isRunning()) {
attrOp.pause();
} else if (attrOp.isPaused()) {
@@ -6101,7 +6278,7 @@
}
}
- private void notifyWatchersOfChange(int code, int uid) {
+ private void notifyWatchersOnDefaultDevice(int code, int uid) {
final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
synchronized (this) {
modeChangedListenerSet = mOpModeWatchers.get(code);
@@ -6109,8 +6286,7 @@
return;
}
}
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ notifyOpChanged(modeChangedListenerSet, code, uid, null, PERSISTENT_DEVICE_ID_DEFAULT);
}
@Override
@@ -6582,6 +6758,25 @@
return packageNames;
}
+ @NonNull private String getPersistentId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ String persistentId =
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId);
+ if (persistentId == null) {
+ persistentId = mKnownDeviceIds.get(virtualDeviceId);
+ }
+ if (persistentId != null) {
+ return persistentId;
+ }
+ throw new IllegalStateException(
+ "Requested persistentId for invalid virtualDeviceId: " + virtualDeviceId);
+ }
+
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
@@ -6713,12 +6908,14 @@
}
if (restrictionState.setRestriction(code, restricted)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
- UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, AppOpsService.this,
+ code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
- code, restricted, UserHandle.USER_ALL));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice,
+ AppOpsService.this, code, restricted, UserHandle.USER_ALL));
}
if (restrictionState.isDefault()) {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 0ded75a..94baf88 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -42,6 +42,7 @@
final class AttributedOp {
private final @NonNull AppOpsService mAppOpsService;
public final @Nullable String tag;
+ public final @NonNull String persistentDeviceId;
public final @NonNull AppOpsService.Op parent;
/**
@@ -81,9 +82,10 @@
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
- @NonNull AppOpsService.Op parent) {
+ @NonNull String persistentDeviceId, @NonNull AppOpsService.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
+ this.persistentDeviceId = persistentDeviceId;
this.parent = parent;
}
@@ -196,23 +198,26 @@
*/
public void started(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags, /* triggeredByUidStateChange */ false,
- /* isStarted */ true, attributionFlags, attributionChainId);
+ proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
+ /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
+ attributionChainId);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- boolean triggeredByUidStateChange, boolean isStarted, @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
+ boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, attributionFlags, attributionChainId);
+ parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ attributionChainId);
}
if (isStarted && mInProgressEvents == null) {
@@ -227,7 +232,7 @@
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag,
+ SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
@@ -320,8 +325,8 @@
// TODO ntmyren: Also callback for single attribution tag activity changes
if (!triggeredByUidStateChange && !parent.isRunning()) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(),
+ false, event.getAttributionFlags(), event.getAttributionChainId());
}
}
}
@@ -362,11 +367,13 @@
*/
public void createPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- uidState, flags, false, false, attributionFlags, attributionChainId);
+ proxyVirtualDeviceId, uidState, flags, false, false,
+ attributionFlags, attributionChainId);
}
/**
@@ -387,7 +394,7 @@
finishOrPause(event.getClientId(), false, true);
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, false,
+ parent.packageName, tag, event.getVirtualDeviceId(), false,
event.getAttributionFlags(), event.getAttributionChainId());
}
mInProgressEvents = null;
@@ -419,14 +426,15 @@
event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, event.getAttributionFlags(),
- event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), true,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
// Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
// TODO ntmyren: figure out how to get the real mode.
mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), event.getFlags(),
+ MODE_ALLOWED, START_TYPE_RESUMED, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
mPausedInProgressEvents = null;
}
@@ -488,13 +496,15 @@
// previously removed unfinished start counts back
if (proxy != null) {
startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(), newState,
- event.getFlags(), true, isRunning,
+ proxy.getPackageName(), proxy.getAttributionTag(),
+ event.getVirtualDeviceId(), newState, event.getFlags(),
+ true, isRunning,
event.getAttributionFlags(), event.getAttributionChainId());
} else {
startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- newState, event.getFlags(), true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ event.getVirtualDeviceId(), newState, event.getFlags(), true,
+ isRunning, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -508,7 +518,7 @@
"Cannot switch to new uidState " + newState);
}
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), false,
eventAttributionFlags, eventAttributionChainId);
}
}
@@ -644,6 +654,9 @@
/** Id of the client that started the event */
private @NonNull IBinder mClientId;
+ /** virtual device id */
+ private int mVirtualDeviceId;
+
/** The attribution tag for this operation */
private @Nullable String mAttributionTag;
@@ -685,7 +698,7 @@
* @throws RemoteException If the client is dying
*/
InProgressStartOpEvent(long startTime, long startElapsedTime,
- @NonNull IBinder clientId, @Nullable String attributionTag,
+ @NonNull IBinder clientId, int virtualDeviceId, @Nullable String attributionTag,
@NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
@Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
@@ -693,6 +706,7 @@
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
+ mVirtualDeviceId = virtualDeviceId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
mUidState = uidState;
@@ -737,7 +751,7 @@
* @throws RemoteException If the client is dying
*/
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath,
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
@Nullable AppOpsManager.OpEventProxyInfo proxy,
@AppOpsManager.AttributionFlags int attributionFlags,
@@ -749,6 +763,7 @@
mClientId = clientId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
+ mVirtualDeviceId = virtualDeviceId;
mUidState = uidState;
mFlags = flags;
@@ -802,6 +817,11 @@
return mAttributionChainId;
}
+ /** @return virtual device id for the access */
+ public int getVirtualDeviceId() {
+ return mVirtualDeviceId;
+ }
+
public void setStartTime(long startTime) {
mStartTime = startTime;
}
@@ -824,11 +844,11 @@
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
+ int proxyUid, @Nullable String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
InProgressStartOpEvent recycled = acquire();
@@ -839,14 +859,15 @@
}
if (recycled != null) {
- recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
- uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+ recycled.reinit(startTime, elapsedTime, clientId, attributionTag, virtualDeviceId,
+ onDeath, uidState, flags, proxyInfo, attributionFlags, attributionChainId,
mOpEventProxyInfoPool);
return recycled;
}
- return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
- onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, virtualDeviceId,
+ attributionTag, onDeath, uidState, proxyInfo, flags, attributionFlags,
+ attributionChainId);
}
}
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 1d1a9e7..f5f34c1 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -16,8 +16,11 @@
package com.android.server.appop;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.RemoteException;
+import java.util.Objects;
+
/**
* Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
* change.
@@ -95,6 +98,20 @@
throws RemoteException;
/**
+ * Method that should be triggered when the app-op's mode is changed.
+ * @param op app-op whose mode-change is being listened to.
+ * @param uid user-is associated with the app-op.
+ * @param packageName package name associated with the app-op.
+ * @param persistentDeviceId device associated with the app-op.
+ */
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ if (Objects.equals(persistentDeviceId, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpModeChanged(op, uid, packageName);
+ }
+ }
+
+ /**
* Return human readable string representing the listener.
*/
public abstract String toString();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index aa6a0f1..fbd32a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -112,10 +112,8 @@
getLogger().logOnError(getContext(), getOperationContext(),
errorCode, vendorCode, getTargetUserId());
try {
- if (getListener() != null) {
- mShouldSendErrorToClient = false;
- getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
- }
+ mShouldSendErrorToClient = false;
+ getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -147,9 +145,7 @@
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -181,7 +177,7 @@
}
try {
- if (getListener() != null && shouldSend) {
+ if (shouldSend) {
getListener().onAcquired(getSensorId(), acquiredInfo, vendorCode);
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index f9568ea..506b456 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -265,17 +265,13 @@
Slog.d(TAG, "Skipping addAuthToken");
}
try {
- if (listener != null) {
- if (!mIsRestricted) {
- listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
- getTargetUserId(), mIsStrongBiometric);
- } else {
- listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
- byteToken,
- getTargetUserId(), mIsStrongBiometric);
- }
+ if (!mIsRestricted) {
+ listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
+ getTargetUserId(), mIsStrongBiometric);
} else {
- Slog.e(TAG, "Received successful auth, but client was not listening");
+ listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
+ byteToken,
+ getTargetUserId(), mIsStrongBiometric);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
@@ -301,11 +297,7 @@
}
try {
- if (listener != null) {
- listener.onAuthenticationFailed(getSensorId());
- } else {
- Slog.e(TAG, "Received failed auth, but client was not listening");
- }
+ listener.onAuthenticationFailed(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
mCallback.onClientFinished(this, false);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 0216e49..a408852 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -55,7 +56,7 @@
@Nullable private IBinder mToken;
private long mRequestId;
- @Nullable private ClientMonitorCallbackConverter mListener;
+ @NonNull private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
@@ -95,7 +96,8 @@
mContext = context;
mToken = token;
mRequestId = -1;
- mListener = listener;
+ mListener = listener == null ? new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()) : listener;
mTargetUserId = userId;
mOwner = owner;
mCookie = cookie;
@@ -199,7 +201,7 @@
}
mToken = null;
if (clearListener) {
- mListener = null;
+ mListener = new ClientMonitorCallbackConverter(new IBiometricSensorReceiver.Default());
}
}
@@ -233,8 +235,8 @@
return mOwner;
}
- @Nullable
- public final ClientMonitorCallbackConverter getListener() {
+ @NonNull
+ protected ClientMonitorCallbackConverter getListener() {
return mListener;
}
@@ -312,9 +314,7 @@
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
ClientMonitorCallbackConverter listener = getListener();
- if (listener != null) {
- listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 2c4d30b..8e7004d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -82,9 +82,7 @@
final ClientMonitorCallbackConverter listener = getListener();
try {
- if (listener != null) {
- listener.onEnrollResult(identifier, remaining);
- }
+ listener.onEnrollResult(identifier, remaining);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 45ffa23..d2ef278 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -74,13 +74,9 @@
if (identifier == null) {
Slog.e(TAG, "identifier was null, skipping onRemove()");
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(),
- BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
- 0 /* vendorCode */);
- } else {
- Slog.e(TAG, "Error, listener was null, not sending onError callback");
- }
+ getListener().onError(getSensorId(), getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+ 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to send error to client for onRemoved", e);
}
@@ -93,9 +89,7 @@
identifier.getBiometricId());
try {
- if (getListener() != null) {
- getListener().onRemoved(identifier, remaining);
- }
+ getListener().onRemoved(identifier, remaining);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify Removed:", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f35de93..415d294 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -337,7 +337,7 @@
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onAuthenticationFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f5c4529..5f370f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -152,7 +152,7 @@
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onEnrollmentFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index e404bd2..cf45eb8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -58,11 +58,6 @@
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 9812536..47aaeec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -61,9 +61,7 @@
@Override
public void unableToStart() {
try {
- if (getListener() != null) {
- getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
- }
+ getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -85,9 +83,7 @@
featureState[0] = result.value;
mValue = result.value;
- if (getListener() != null) {
- getListener().onFeatureGet(result.status == Status.OK, features, featureState);
- }
+ getListener().onFeatureGet(result.status == Status.OK, features, featureState);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 6912961..e0fd44b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -450,9 +450,7 @@
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -471,9 +469,7 @@
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index a7fb774..cb220b9e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -175,11 +175,7 @@
vibrateSuccess();
try {
- if (getListener() != null) {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } else {
- Slog.e(TAG, "Listener is null!");
- }
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when sending onDetected", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 3fb9223..225bd59 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -305,9 +305,7 @@
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
}
@@ -325,9 +323,7 @@
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ce693ff..d2f36ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -59,11 +59,6 @@
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 9232e11..f857946 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -28,6 +28,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -367,7 +368,8 @@
final IBinder token = client.getToken();
final long operationId = authClient.getOperationId();
final int cookie = client.getCookie();
- final ClientMonitorCallbackConverter listener = client.getListener();
+ final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
+ new IFingerprintServiceReceiver.Default());
final boolean restricted = authClient.isRestricted();
final int statsClient = client.getLogger().getStatsClient();
final boolean isKeyguard = authClient.isKeyguard();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7a329e9..60c532c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -279,12 +279,10 @@
mALSProbeCallback.getProbe().enable();
UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerDown(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerDown(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
@@ -295,12 +293,10 @@
mALSProbeCallback.getProbe().disable();
UdfpsHelper.onFingerUp(getFreshDaemon());
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerUp(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerUp(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 6e029d2..50e48fe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -153,12 +153,10 @@
final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
pm.incrementAuthForUser(getTargetUserId(), authenticated);
- if (getListener() != null) {
- try {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onDetected", e);
- }
+ try {
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when sending onDetected", e);
}
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index a796544..458fd82 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1292,7 +1292,7 @@
return;
}
if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
- nfcAdapter.setReaderMode(enablePolling);
+ nfcAdapter.setReaderModePollingEnabled(enablePolling);
}
private static int[] toArray(Collection<Integer> c) {
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 0f40ca0..1715254 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,8 +16,6 @@
package com.android.server.graphics.fonts;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
import android.annotation.NonNull;
@@ -580,11 +578,11 @@
return null;
}
resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
- font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
+ font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(),
+ null /* family name */, FontConfig.Font.VAR_TYPE_AXES_NONE));
}
FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
- LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT,
- VARIABLE_FONT_FAMILY_TYPE_NONE);
+ LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig.NamedFamilyList(Collections.singletonList(family),
fontFamily.getName());
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 30a4f31..3c3cfe6 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -45,6 +45,13 @@
@VisibleForTesting
static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
+ // State in which the action is delayed. If the action starts and
+ // {@link PowerManager#isInteractive} returns false, it could indicate the beginning of a
+ // standby process. In this scenario, the action will be removed when
+ // {@link HdmiCecLocalDeviceSource#disableDevice} is called, therefore we delay the action.
+ @VisibleForTesting
+ static final int STATE_CHECK_STANDBY_PROCESS_STARTED = 2;
+
// The maximum number of times we send <Give Device Power Status> before we give up.
// We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
private static final int LOOP_COUNTER_MAX = 10;
@@ -87,6 +94,22 @@
boolean start() {
// Because only source device can create this action, it's safe to cast.
mSource = source();
+
+ if (!mSource.mService.getPowerManager().isInteractive()) {
+ Slog.d(TAG, "PowerManager is not interactive. Delay the action to check if standby"
+ + " started!");
+ mState = STATE_CHECK_STANDBY_PROCESS_STARTED;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ startAction();
+ }
+
+ return true;
+ }
+
+ private void startAction() {
+ Slog.i(TAG, "Start action.");
+
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
@@ -116,12 +139,11 @@
maySendActiveSource();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ return;
}
}
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- return true;
}
private void setAndBroadcastActiveSource() {
@@ -174,14 +196,22 @@
if (mState != state) {
return;
}
- if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
- if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
- queryDevicePowerStatus();
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
- } else {
- // Couldn't wake up the TV for whatever reason. Report failure.
- finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
- }
+ switch (state) {
+ case STATE_WAITING_FOR_REPORT_POWER_STATUS:
+ if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+ queryDevicePowerStatus();
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ // Couldn't wake up the TV for whatever reason. Report failure.
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+ }
+ return;
+ case STATE_CHECK_STANDBY_PROCESS_STARTED:
+ Slog.d(TAG, "Action was not removed, start the action.");
+ startAction();
+ return;
+ default:
+ return;
}
}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 165dfe4..5ffc380 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -64,6 +64,8 @@
(reason) -> updateTouchpadNaturalScrollingEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_TO_CLICK),
(reason) -> updateTouchpadTapToClickEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_DRAGGING),
+ (reason) -> updateTouchpadTapDraggingEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
(reason) -> updateTouchpadRightClickZoneEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
@@ -158,6 +160,10 @@
mNative.setTouchpadTapToClickEnabled(InputSettings.useTouchpadTapToClick(mContext));
}
+ private void updateTouchpadTapDraggingEnabled() {
+ mNative.setTouchpadTapDraggingEnabled(InputSettings.useTouchpadTapDragging(mContext));
+ }
+
private void updateTouchpadRightClickZoneEnabled() {
mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index bc82078..e5f3484 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -129,6 +129,8 @@
void setTouchpadTapToClickEnabled(boolean enabled);
+ void setTouchpadTapDraggingEnabled(boolean enabled);
+
void setTouchpadRightClickZoneEnabled(boolean enabled);
void setShowTouches(boolean enabled);
@@ -377,6 +379,9 @@
public native void setTouchpadTapToClickEnabled(boolean enabled);
@Override
+ public native void setTouchpadTapDraggingEnabled(boolean enabled);
+
+ @Override
public native void setTouchpadRightClickZoneEnabled(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f031b7b..0f2af31 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3659,6 +3659,15 @@
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
}
+ // The user represented by userId, must be running.
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.w(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3666,20 +3675,43 @@
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ // If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third arty code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
getSelectedMethodIdLocked(), getSequenceNumberLocked(),
false /* isInputMethodSuppressingSpellChecker */);
}
+ final ClientState cs = mClientController.getClient(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("Unknown client " + client.asBinder());
+ }
final long ident = Binder.clearCallingIdentity();
try {
+ // Verify if IMMS is in the process of switching user.
+ if (mUserSwitchHandlerTask != null) {
+ // There is already an on-going pending user switch task.
+ final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+ if (userId == nextUserId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
+ mSettings.getUserId(), false /* enabledOnly */);
+ for (int profileId : profileIdsWithDisabled) {
+ if (profileId == userId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ }
+ return InputBindResult.INVALID_USER;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3708,7 +3740,7 @@
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3721,23 +3753,9 @@
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ " userId=" + userId
- + " imeDispatcher=" + imeDispatcher);
+ + " imeDispatcher=" + imeDispatcher
+ + " cs=" + cs);
}
-
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.w(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
-
- final ClientState cs = mClientController.getClient(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException("Unknown client " + client.asBinder());
- }
-
final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
switch (imeClientFocus) {
@@ -3759,24 +3777,6 @@
return InputBindResult.INVALID_DISPLAY_ID;
}
- if (mUserSwitchHandlerTask != null) {
- // There is already an on-going pending user switch task.
- final int nextUserId = mUserSwitchHandlerTask.mToUserId;
- if (userId == nextUserId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
- for (int profileId : profileIdsWithDisabled) {
- if (profileId == userId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- }
- return InputBindResult.INVALID_USER;
- }
-
final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
// In case mShowForced flag affects the next client to keep IME visible, when the current
// client is leaving due to the next focused client, we clear mShowForced flag when the
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index fb95632..41ff415 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -210,6 +210,9 @@
mDefaultConfig = readDefaultConfig(mContext.getResources());
updateDefaultAutomaticRuleNames();
+ if (Flags.modesApi()) {
+ updateDefaultAutomaticRulePolicies();
+ }
mConfig = mDefaultConfig.copy();
synchronized (mConfigsArrayLock) {
mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -1966,6 +1969,21 @@
}
}
+ // Updates the policies in the default automatic rules (provided via default XML config) to
+ // be fully filled in default values.
+ private void updateDefaultAutomaticRulePolicies() {
+ if (!Flags.modesApi()) {
+ // Should be checked before calling, but just in case.
+ return;
+ }
+ ZenPolicy defaultPolicy = mDefaultConfig.toZenPolicy();
+ for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
+ if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+ rule.zenPolicy = defaultPolicy.copy();
+ }
+ }
+ }
+
@VisibleForTesting
protected void applyRestrictions() {
final boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 47967db..dbff442 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -63,4 +63,11 @@
namespace: "systemui"
description: "Timing test, no functionality"
bug: "316931130"
+}
+
+flag {
+ name: "notification_custom_view_uri_restriction"
+ namespace: "systemui"
+ description: "This flag enables memory restriction of notifications holding custom views with Uri Bitmaps"
+ bug: "270553691"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 0555d90..0be8e6e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -586,7 +586,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, filterCallingUid);
+ list, false, intent, resolvedType, filterCallingUid);
}
}
} else {
@@ -616,7 +616,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, filterCallingUid);
+ list, false, originalIntent, resolvedType, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -700,7 +700,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, callingUid);
+ list, false, intent, resolvedType, callingUid);
}
}
} else {
@@ -712,7 +712,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, callingUid);
+ list, false, originalIntent, resolvedType, callingUid);
}
return list;
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index a10bae9..3abf3a5 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -16,7 +16,10 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.os.Trace.TRACE_TAG_DALVIK;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
@@ -27,6 +30,8 @@
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
@@ -45,6 +50,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
@@ -54,6 +61,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -1091,4 +1099,73 @@
+ " has unsupported status " + status);
}
}
+
+ /**
+ * Returns DexoptOptions by the given InstallRequest.
+ */
+ static DexoptOptions getDexoptOptionsByInstallRequest(InstallRequest installRequest,
+ DexManager dexManager) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+ final boolean isBackupOrRestore =
+ installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
+ || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
+ final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
+ | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
+ // Compute the compilation reason from the installation scenario.
+ final int compilationReason =
+ dexManager.getCompilationReasonForInstallScenario(
+ installRequest.getInstallScenario());
+ return new DexoptOptions(packageName, compilationReason, dexoptFlags);
+ }
+
+ /**
+ * Use ArtService to perform dexopt by the given InstallRequest.
+ */
+ static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest,
+ DexoptOptions dexoptOptions) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+
+ PackageManagerLocal packageManagerLocal =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ packageManagerLocal.withFilteredSnapshot()) {
+ 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 = getArtManagerLocal().dexoptPackage(
+ snapshot, packageName, params);
+
+ return dexOptResult;
+ }
+ }
+
+ /**
+ * Returns whether to perform dexopt by the given InstallRequest.
+ */
+ static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions,
+ Context context) {
+ final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
+ final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final AndroidPackage pkg = ps.getPkg();
+ final boolean onIncremental = isIncrementalPath(ps.getPathString());
+
+ return (!instantApp || Global.getInt(context.getContentResolver(),
+ Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
+ && pkg != null
+ && !pkg.isDebuggable()
+ && (!onIncremental)
+ && dexoptOptions.isCompilationEnabled()
+ && !isApex;
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e70c5ea..28f3d59 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -32,8 +32,6 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
@@ -170,10 +168,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
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.criticalevents.CriticalEventLog;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -800,10 +795,27 @@
"restoreAndPostInstall userId=" + userId + " package=" + request.getPkg());
}
- // A restore should be requested at this point if (a) the install
- // succeeded, (b) the operation is not an update.
+ PackageSetting packageSetting = null;
+
final boolean update = request.isUpdate();
- boolean doRestore = !update && request.getPkg() != null;
+ boolean doRestore = false;
+ if (request.getPkg() != null && !request.isArchived()) {
+ // A restore should be requested at this point:
+ // if the install succeeded and it's not an archived install
+ if (!update) {
+ // AND the operation is not an update,
+ doRestore = true;
+ } else {
+ // OR the package has never been restored.
+ String packageName = request.getPkg().getPackageName();
+ synchronized (mPm.mLock) {
+ packageSetting = mPm.mSettings.getPackageLPr(packageName);
+ if (packageSetting != null && packageSetting.isPendingRestore()) {
+ doRestore = true;
+ }
+ }
+ }
+ }
// Set up the post-install work request bookkeeping. This will be used
// and cleaned up by the post-install event handling regardless of whether
@@ -833,7 +845,13 @@
doRestore = performRollbackManagerRestore(userId, token, request);
}
- if (!doRestore) {
+ if (doRestore) {
+ if (packageSetting != null) {
+ synchronized (mPm.mLock) {
+ packageSetting.setPendingRestore(false);
+ }
+ }
+ } else {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
@@ -2464,8 +2482,6 @@
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
- final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
- final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
final String codePath = ps.getPathString();
@@ -2507,28 +2523,14 @@
}
}
- // Compute the compilation reason from the installation scenario.
- final int compilationReason =
- mDexManager.getCompilationReasonForInstallScenario(
- installRequest.getInstallScenario());
-
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
//
// Also, don't fail application installs if the dexopt step fails.
- final boolean isBackupOrRestore =
- installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
- || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
-
- final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
- | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
- DexoptOptions dexoptOptions =
- new DexoptOptions(packageName, compilationReason, dexoptFlags);
-
+ DexoptOptions dexoptOptions = DexOptHelper.getDexoptOptionsByInstallRequest(
+ installRequest, mDexManager);
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
@@ -2555,16 +2557,9 @@
//
// TODO(b/174695087): instantApp and onIncremental should be removed and their install
// path moved to SCENARIO_FAST.
- final boolean performDexopt =
- (!instantApp || android.provider.Settings.Global.getInt(
- mContext.getContentResolver(),
- android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
- && pkg != null
- && !pkg.isDebuggable()
- && (!onIncremental)
- && dexoptOptions.isCompilationEnabled()
- && !isApex;
+ final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
+ dexoptOptions, mContext);
if (performDexopt) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
@@ -2579,23 +2574,9 @@
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
if (useArtService()) {
- PackageManagerLocal packageManagerLocal =
- LocalManagerRegistry.getManager(PackageManagerLocal.class);
- try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
- 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);
- }
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
} else {
try {
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
@@ -2919,7 +2900,7 @@
mPm.scheduleDeferredNoKillPostDelete(args);
if (Flags.improveInstallDontKill()) {
synchronized (mPm.mInstallLock) {
- PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller,
+ PackageManagerServiceUtils.linkFilesToOldDirs(mPm.mInstaller,
packageName, pkgSetting.getPath(), pkgSetting.getOldPaths());
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b5346a3..43328fc 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,10 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
+import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -43,6 +47,7 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyCache;
@@ -213,6 +218,7 @@
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageManagerInternal mPackageManagerInternal;
+ private final AppOpsManager mAppOpsManager;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
@@ -253,6 +259,7 @@
LocalServices.getService(ShortcutServiceInternal.class));
mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mShortcutServiceInternal.addListener(mPackageMonitor);
mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
@@ -1719,7 +1726,8 @@
.scheme("android-app")
.authority(callingPackage)
.build())
- .setPackage(installerPackageName);
+ .setPackage(installerPackageName)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Override
@@ -1997,6 +2005,23 @@
}
}
+ @Override
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ int callingUid = Binder.getCallingUid();
+ Binder.withCleanCallingIdentity(
+ () -> {
+ mAppOpsManager.setUidMode(
+ OP_ARCHIVE_ICON_OVERLAY,
+ callingUid,
+ enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED);
+ mAppOpsManager.setUidMode(
+ OP_UNARCHIVAL_CONFIRMATION,
+ callingUid,
+ enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED);
+ });
+ }
+
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 32f5646..339b1e7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -19,7 +19,7 @@
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
-import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
@@ -275,19 +275,26 @@
Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
try {
- // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
- false /* showUnarchivalConfirmation= */);
+ getAppOpsManager().checkOp(
+ AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
+ == MODE_ALLOWED);
} catch (Throwable t) {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
t.getLocalizedMessage()));
- return START_ABORTED;
}
- return START_SUCCESS;
+ // We return STATUS_ABORTED because:
+ // 1. Archived App is not actually present during activity start. Hence the unarchival
+ // start should be treated as an error code.
+ // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
+ // experience.
+ // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
+ // aborting activity options, animations etc in the Windows Manager.
+ return START_ABORTED;
}
/**
@@ -796,7 +803,8 @@
* <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
* launcher activities, only one of the icons is returned arbitrarily.
*/
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ String callingPackageName) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -819,7 +827,13 @@
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+ Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
+ if (getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
+ == MODE_ALLOWED) {
+ icon = includeCloudOverlay(icon);
+ }
+ return icon;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9617098..b705e84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -598,6 +598,8 @@
static final int DEFAULT_FILE_ACCESS_MODE = 0644;
+ static final int DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE = 0755;
+
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -1550,6 +1552,8 @@
}
pkgSetting
.setPkg(null)
+ // This package was installed as archived. Need to mark it for later restore.
+ .setPendingRestore(true)
.modifyUserState(userId)
.setInstalled(false)
.setArchiveState(archiveState);
@@ -6414,8 +6418,10 @@
}
@Override
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
- return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ @NonNull String callingPackageName) {
+ return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user,
+ callingPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 8531692..4f9ed03 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -23,6 +23,7 @@
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
+import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
@@ -31,6 +32,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
+import static com.android.server.pm.PackageManagerService.DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -44,7 +46,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
@@ -198,7 +200,7 @@
*/
@Overridable
@ChangeId
- @Disabled /* Enforcement reverted in T: b/274147456 */
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1192,8 +1194,7 @@
public static void applyEnforceIntentFilterMatching(
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
- Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int filterCallingUid) {
+ Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
// Do not enforce filter matching when the caller is system or root
@@ -1203,10 +1204,9 @@
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-
- final boolean enforce = compat.isChangeEnabledByUidInternal(
- ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
+ final boolean enforce = android.security.Flags.enforceIntentFilterMatch()
+ && compat.isChangeEnabledByUidInternal(
+ ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
@@ -1237,8 +1237,7 @@
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
- if (IntentResolver.intentMatchesFilter(
- intentFilter, intent, resolvedType, defaultOnly)) {
+ if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
match = true;
break;
}
@@ -1557,7 +1556,7 @@
return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
}
- public static void linkSplitsToOldDirs(@NonNull Installer installer,
+ public static void linkFilesToOldDirs(@NonNull Installer installer,
@NonNull String packageName,
@NonNull File newPath,
@Nullable Set<File> oldPaths) {
@@ -1572,56 +1571,108 @@
if (filesInNewPath == null || filesInNewPath.length == 0) {
return;
}
- final List<String> splitApkNames = new ArrayList<String>();
- for (int i = 0; i < filesInNewPath.length; i++) {
- if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) {
- splitApkNames.add(filesInNewPath[i].getName());
+ final List<File> splitApks = new ArrayList<>();
+ for (File file : filesInNewPath) {
+ if (!file.isDirectory() && file.toString().endsWith(".apk")) {
+ splitApks.add(file);
}
}
- final int numSplits = splitApkNames.size();
- if (numSplits == 0) {
+ if (splitApks.isEmpty()) {
return;
}
+ final File[] splitApkNames = splitApks.toArray(new File[0]);
for (File oldPath : oldPaths) {
if (!oldPath.exists()) {
continue;
}
- for (int i = 0; i < numSplits; i++) {
- final String splitApkName = splitApkNames.get(i);
- final File linkedSplit = new File(oldPath, splitApkName);
- if (linkedSplit.exists()) {
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Skipping existing linked split <"
- + linkedSplit + ">");
- }
- continue;
- }
- final File sourceSplit = new File(newPath, splitApkName);
- try {
- installer.linkFile(packageName, splitApkName,
- newPath.getAbsolutePath(), oldPath.getAbsolutePath());
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Linked <"
- + sourceSplit + "> to <" + linkedSplit + ">");
- }
- } catch (Installer.InstallerException e) {
- Slog.w(PackageManagerService.TAG, "Failed to link split <"
- + sourceSplit + " > to <" + linkedSplit + ">", e);
- continue;
- }
- try {
- Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
- } catch (ErrnoException e) {
- Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <"
- + linkedSplit + ">", e);
- continue;
- }
- if (!SELinux.restorecon(linkedSplit)) {
- Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <"
- + linkedSplit + ">");
- }
+ linkFilesAndSetModes(installer, packageName, newPath, oldPath, splitApkNames,
+ DEFAULT_FILE_ACCESS_MODE);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB_DIR_NAME);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB64_DIR_NAME);
+ }
+
+ }
+
+ private static void linkNativeLibraries(@NonNull Installer installer,
+ @NonNull String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath,
+ @NonNull String libDirName) {
+ final File sourceLibDir = new File(sourcePath, libDirName);
+ if (!sourceLibDir.exists()) {
+ return;
+ }
+ final File targetLibDir = new File(targetPath, libDirName);
+ if (!targetLibDir.exists()) {
+ try {
+ NativeLibraryHelper.createNativeLibrarySubdir(targetLibDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library dir at <"
+ + targetLibDir + ">", e);
+ return;
}
}
- //TODO(b/291212866): support native libs
+ final File[] archs = sourceLibDir.listFiles();
+ if (archs == null) {
+ return;
+ }
+ for (File arch : archs) {
+ final File targetArchDir = new File(targetLibDir, arch.getName());
+ if (!targetArchDir.exists()) {
+ try {
+ NativeLibraryHelper.createNativeLibrarySubdir(targetArchDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library subdir at <"
+ + targetArchDir + ">", e);
+ continue;
+ }
+ }
+ final File sourceArchDir = new File(sourceLibDir, arch.getName());
+ final File[] files = sourceArchDir.listFiles();
+ if (files == null || files.length == 0) {
+ continue;
+ }
+ linkFilesAndSetModes(installer, packageName, sourceArchDir, targetArchDir, files,
+ DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE);
+ }
+ }
+
+ // Link the files with specified names from under the sourcePath to be under the targetPath
+ private static void linkFilesAndSetModes(@NonNull Installer installer, String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath, @NonNull File[] files, int mode) {
+ for (File file : files) {
+ final String fileName = file.getName();
+ final File sourceFile = new File(sourcePath, fileName);
+ final File targetFile = new File(targetPath, fileName);
+ if (targetFile.exists()) {
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Skipping existing linked file <"
+ + targetFile + ">");
+ }
+ continue;
+ }
+ try {
+ installer.linkFile(packageName, fileName,
+ sourcePath.getAbsolutePath(), targetPath.getAbsolutePath());
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Linked <"
+ + sourceFile + "> to <" + targetFile + ">");
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to link native library <"
+ + sourceFile + "> to <" + targetFile + ">", e);
+ continue;
+ }
+ try {
+ Os.chmod(targetFile.getAbsolutePath(), mode);
+ } catch (ErrnoException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to set mode for linked file <"
+ + targetFile + ">", e);
+ continue;
+ }
+ if (!SELinux.restorecon(targetFile)) {
+ Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked file <"
+ + targetFile + ">");
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 74c482b..f474d32 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -94,7 +94,8 @@
INSTALL_PERMISSION_FIXED,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE,
- SCANNED_AS_STOPPED_SYSTEM_APP
+ SCANNED_AS_STOPPED_SYSTEM_APP,
+ PENDING_RESTORE,
})
public @interface Flags {
}
@@ -102,6 +103,7 @@
private static final int UPDATE_AVAILABLE = 1 << 1;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
+ private static final int PENDING_RESTORE = 1 << 4;
}
private int mBooleans;
@@ -543,6 +545,20 @@
return mSharedUserAppId > 0;
}
+ /**
+ * @see PackageState#isPendingRestore()
+ */
+ public PackageSetting setPendingRestore(boolean value) {
+ setBoolean(Booleans.PENDING_RESTORE, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isPendingRestore() {
+ return getBoolean(Booleans.PENDING_RESTORE);
+ }
+
@Override
public String toString() {
return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 203e1de..b664e39 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -459,7 +459,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver, list, true, intent,
- resolvedType, flags, filterCallingUid);
+ resolvedType, filterCallingUid);
}
}
} else {
@@ -485,7 +485,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver,
- list, true, originalIntent, resolvedType, flags, filterCallingUid);
+ list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7cf1d33..c7ee649 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4537,6 +4537,8 @@
t.traceBegin("createNewUser-" + userHandle);
Installer.Batch batch = new Installer.Batch();
final boolean skipPackageAllowList = userTypeInstallablePackages == null;
+ // Use the same timestamp for all system apps that are to be installed on the new user
+ final long currentTimeMillis = System.currentTimeMillis();
synchronized (mLock) {
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
@@ -4552,6 +4554,9 @@
ps.getPackageName()));
// Only system apps are initially installed.
ps.setInstalled(shouldReallyInstall, userHandle);
+ if (Flags.fixSystemAppsFirstInstallTime() && shouldReallyInstall) {
+ ps.setFirstInstallTime(currentTimeMillis, userHandle);
+ }
// Non-Apex system apps, that are not included in the allowlist in
// initialNonStoppedSystemPackages, should be marked as stopped by default.
@@ -4878,7 +4883,7 @@
@NeverCompile // Avoid size overhead of debugging code.
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag,
- ArraySet<String> permissionNames, PackageSetting ps,
+ ArraySet<String> permissionNames, @NonNull PackageSetting ps,
LegacyPermissionState permissionsState, SimpleDateFormat sdf, Date date,
List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) {
AndroidPackage pkg = ps.getPkg();
@@ -5017,6 +5022,10 @@
pw.print(prefix); pw.print(" privateFlags="); printFlags(pw,
privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println();
}
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.print(" pendingRestore=true");
+ pw.println();
+ }
if (!pkg.isUpdatableSystem()) {
pw.print(prefix); pw.print(" updatableSystem=false");
pw.println();
@@ -5227,6 +5236,9 @@
pw.print(prefix); pw.print(" privatePkgFlags="); printFlags(pw, ps.getPrivateFlags(),
PRIVATE_FLAG_DUMP_SPEC);
pw.println();
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.println(" pendingRestore=true");
+ }
pw.print(prefix); pw.print(" apexModuleName="); pw.println(ps.getApexModuleName());
if (pkg != null && pkg.getOverlayTarget() != null) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a7ae4eb..e0ee199 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -265,6 +265,14 @@
*/
boolean hasSharedUser();
+
+ /**
+ * Whether this app needs to be restore during next install/update.
+ * E.g. if an app was installed as archived and never had a chance to restore its data.
+ * @hide
+ */
+ boolean isPendingRestore();
+
/**
* Retrieves the shared user app ID. Note that the actual shared user data is not available here
* and must be queried separately.
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 61348b5..f15646f 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -43,6 +43,7 @@
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
@@ -234,7 +235,12 @@
this::synchronizeUidPermissionsAndAppOpsAsync);
mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, @Nullable String packageName) {
+ public void opChanged(int op, int uid, @Nullable String packageName,
+ String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ return;
+ }
if (packageName != null) {
synchronizeUidPermissionsAndAppOpsAsync(uid);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1fdcc64..51790b8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -75,6 +75,7 @@
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
+import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
@@ -3779,6 +3780,11 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
+ case KeyEvent.KEYCODE_SCREENSHOT:
+ if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ }
+ return true;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -5022,6 +5028,12 @@
case KeyEvent.KEYCODE_MACRO_4:
result &= ~ACTION_PASS_TO_USER;
break;
+ case KeyEvent.KEYCODE_EMOJI_PICKER:
+ if (!emojiAndScreenshotKeycodesAvailable()) {
+ // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ break;
}
if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b271a03..a4c6959 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.app.ITransientNotificationCallback;
+import android.content.ComponentName;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
@@ -241,4 +242,17 @@
* @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
*/
void showMediaOutputSwitcher(String packageName);
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(ComponentName tile, boolean end);
+
+ /**
+ * Remove the tile from the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
+ void removeQsTile(ComponentName tile);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b21721a..4955358 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -89,6 +89,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -834,6 +835,20 @@
}
}
}
+
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
+ }
+ }
+
+ @Override
+ public void removeQsTile(ComponentName tile) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.remTile(tile);
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
@@ -934,11 +949,26 @@
}
public void addTile(ComponentName component) {
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(component, false);
+ } else {
+ enforceStatusBarOrShell();
+
+ if (mBar != null) {
+ try {
+ mBar.addQsTile(component);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
enforceStatusBarOrShell();
if (mBar != null) {
try {
- mBar.addQsTile(component);
+ mBar.addQsTileToFrontOrEnd(tile, end);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 3abebf8..d102054 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -443,6 +443,8 @@
mPendingSuccessfulUnlock = false;
}
+ // It's okay to use the "Inner" version of isDeviceLocked since they differ only for
+ // profiles, which cannot be switched to and thus don't support trust agents anyway.
if (mTrustManagerService.isDeviceLockedInner(mUserId)) {
onDeviceLocked();
} else {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index dbf777f..2b05993 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -188,25 +188,30 @@
new SparseArray<>();
/**
- * Stores the locked state for users on the device. There are three different type of users
+ * Stores the locked state for users on the device. There are several different types of users
* which are handled slightly differently:
* <ul>
- * <li> Users with real keyguard
+ * <li> Users with real keyguard:
* These are users who can be switched to ({@link UserInfo#supportsSwitchToByUser()}). Their
* locked state is derived by a combination of user secure state, keyguard state, trust agent
* decision and biometric authentication result. These are updated via
* {@link #refreshDeviceLockedForUser(int)} and result stored in {@link #mDeviceLockedForUser}.
- * <li> Managed profiles with unified challenge
- * Managed profile with unified challenge always shares the same locked state as their parent,
+ * <li> Profiles with unified challenge:
+ * Profiles with a unified challenge always share the same locked state as their parent,
* so their locked state is not recorded in {@link #mDeviceLockedForUser}. Instead,
* {@link ITrustManager#isDeviceLocked(int)} always resolves their parent user handle and
* queries its locked state instead.
- * <li> Managed profiles with separate challenge
- * Locked state for profile with separate challenge is determined by other parts of the
- * framework (mostly PowerManager) and pushed to TrustManagerService via
- * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}. Although in a corner case when
- * the profile has a separate but empty challenge, setting its {@link #mDeviceLockedForUser} to
- * {@code false} is actually done by {@link #refreshDeviceLockedForUser(int)}.
+ * <li> Profiles without unified challenge:
+ * The locked state for profiles that do not have a unified challenge (e.g. they have a
+ * separate challenge from their parent, or they have no parent at all) is determined by other
+ * parts of the framework (mostly PowerManager) and pushed to TrustManagerService via
+ * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}.
+ * However, in the case where such a profile has an empty challenge, setting its
+ * {@link #mDeviceLockedForUser} to {@code false} is actually done by
+ * {@link #refreshDeviceLockedForUser(int)}.
+ * (This serves as a corner case for managed profiles with a separate but empty challenge. It
+ * is always currently the case for Communal profiles, for which having a non-empty challenge
+ * is not currently supported.)
* </ul>
* TODO: Rename {@link ITrustManager#setDeviceLockedForUser(int, boolean)} to
* {@code setDeviceLockedForProfile} to better reflect its purpose. Unifying
@@ -794,7 +799,7 @@
/**
* Update the user's locked state. Only applicable to users with a real keyguard
- * ({@link UserInfo#supportsSwitchToByUser}) and unsecured managed profiles.
+ * ({@link UserInfo#supportsSwitchToByUser}) and unsecured profiles.
*
* If this is called due to an unlock operation set unlockedUser to prevent the lock from
* being prematurely reset for that user while keyguard is still in the process of going away.
@@ -826,7 +831,11 @@
boolean secure = mLockPatternUtils.isSecure(id);
if (!info.supportsSwitchToByUser()) {
- if (info.isManagedProfile() && !secure) {
+ if (info.isProfile() && !secure
+ && !mLockPatternUtils.isProfileWithUnifiedChallenge(id)) {
+ // Unsecured profiles need to be explicitly set to false.
+ // However, Unified challenge profiles officially shouldn't have a presence in
+ // mDeviceLockedForUser at all, since that's not how they're tracked.
setDeviceLockedForUser(id, false);
}
continue;
@@ -1853,6 +1862,7 @@
}
}
+ /** If the userId has a parent, returns that parent's userId. Otherwise userId is returned. */
private int resolveProfileParent(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e434df7..089a886 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2791,6 +2791,29 @@
}
@Override
+ public void notifyTvAdSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "notifyTvAdSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyTvAdSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public int getClientPid(String sessionId) {
ensureTunerResourceAccessPermission();
final long identity = Binder.clearCallingIdentity();
@@ -4322,6 +4345,23 @@
}
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTvInputSessionData()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTvInputSessionData(type, data, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTvInputSessionData", e);
+ }
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index eacd3f8..ffce50e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -937,6 +937,41 @@
}
@Override
+ public void sendAppLinkCommand(String serviceId, Bundle command, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "sendAppLinkCommand");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+ if (adServiceState == null) {
+ Slogf.e(TAG, "failed to sendAppLinkCommand - unknown service id "
+ + serviceId);
+ return;
+ }
+ ComponentName componentName = adServiceState.mInfo.getComponent();
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(componentName);
+ if (serviceState == null) {
+ serviceState = new AdServiceState(componentName, serviceId, resolvedUserId);
+ serviceState.addPendingAppLinkCommand(command);
+ userState.mAdServiceStateMap.put(componentName, serviceState);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ } else if (serviceState.mService != null) {
+ serviceState.mService.sendAppLinkCommand(command);
+ } else {
+ serviceState.addPendingAppLinkCommand(command);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in sendAppLinkCommand", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createSession(final ITvAdClient client, final String serviceId, String type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -1320,6 +1355,32 @@
}
@Override
+ public void notifyTvInputSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTvInputSessionData(type=%d)", type);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyTvInputSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyTvInputSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void registerCallback(final ITvAdManagerCallback callback, int userId) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
@@ -4358,6 +4419,110 @@
}
}
+ @Override
+ public void onRequestCurrentVideoBounds() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentChannelUri(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentChannelUri", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestTrackInfoList");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestTrackInfoList(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestTrackInfoList", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentTvInputId", e);
+ }
+ }
+ }
+
+
+ @Override
+ public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestSigning");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestSigning(
+ id, algorithm, alias, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestSigning", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onTvAdSessionData");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onTvAdSessionData(type, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onTvAdSessionData", e);
+ }
+ }
+ }
+
@GuardedBy("mLock")
private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
try {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index ed04e5f..1383708 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,7 +34,7 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,7 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
+ mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0a7872f..c9805c7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,10 +17,10 @@
package com.android.server.vibrator;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.IExternalVibratorService;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.vibrator.Flags;
@@ -57,8 +57,7 @@
private final SparseArray<ScaleLevel> mScaleLevels;
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
-
- private SparseArray<Float> mAdaptiveHapticsScales;
+ private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
@@ -147,7 +146,7 @@
// If adaptive haptics scaling is available for this usage, apply it to the segment.
if (Flags.adaptiveHapticsEnabled()
- && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
+ && mAdaptiveHapticsScales.size() > 0
&& mAdaptiveHapticsScales.contains(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
segment = segment.scale(adaptiveScale);
@@ -187,13 +186,35 @@
}
/**
- * Updates the adaptive haptics scales.
- * @param scales the new vibration scales to apply.
+ * Updates the adaptive haptics scales list by adding or modifying the scale for this usage.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to vibrations of this usage.
*
* @hide
*/
- public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
- mAdaptiveHapticsScales = scales;
+ public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) {
+ mAdaptiveHapticsScales.put(usageHint, scale);
+ }
+
+ /**
+ * Removes the usage from the cached adaptive haptics scales list.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ *
+ * @hide
+ */
+ public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) {
+ mAdaptiveHapticsScales.remove(usageHint);
+ }
+
+ /**
+ * Removes all cached adaptive haptics scales.
+ *
+ * @hide
+ */
+ public void clearAdaptiveHapticsScales() {
+ mAdaptiveHapticsScales.clear();
}
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 839c207..fab0430 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -376,6 +376,25 @@
}
/**
+ * Returns the duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ * @return The request vibration params timeout in milliseconds.
+ * @hide
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return mVibrationConfig.getRequestVibrationParamsTimeoutMs();
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mVibrationConfig.getRequestVibrationParamsForUsages();
+ }
+
+ /**
* Return a {@link VibrationEffect} that should be played if the device do not support given
* {@code effectId}.
*
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 624da80..9cf942e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -21,7 +21,9 @@
import android.os.Build;
import android.os.CombinedVibration;
import android.os.IBinder;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -38,6 +40,8 @@
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
/**
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -65,15 +69,18 @@
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
private final DeviceAdapter mDeviceAdapter;
+ private final VibrationScaler mVibrationScaler;
// Not guarded by lock because it's mostly used to read immutable fields by this conductor.
// This is only modified here at the prepareToStart method which always runs at the vibration
// thread, to update the adapted effect and report start time.
private final HalVibration mVibration;
-
private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+ @Nullable
+ private final CompletableFuture<Void> mRequestVibrationParamsFuture;
+
// Signalling fields.
// Note that vibrator callback signals may happen inside vibrator HAL calls made by the
// VibrationThread, or on an external executor, so this lock should not be held for anything
@@ -97,10 +104,14 @@
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
DeviceAdapter deviceAdapter,
+ VibrationScaler vibrationScaler,
+ CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
+ mVibrationScaler = vibrationScaler;
+ mRequestVibrationParamsFuture = requestVibrationParamsFuture;
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
@@ -143,7 +154,15 @@
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- // Scaling happened before the effect was dispatched to this conductor (or to input devices)
+
+ if (!mVibration.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ if (Flags.adaptiveHapticsEnabled()) {
+ waitForVibrationParamsIfRequired();
+ }
+ mVibration.scaleEffects(mVibrationScaler::scale);
+ }
+
mVibration.adaptToDevice(mDeviceAdapter);
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
mPendingVibrateSteps++;
@@ -361,6 +380,9 @@
+ mSignalCancelImmediate);
}
}
+ if (mRequestVibrationParamsFuture != null) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */true);
+ }
mLock.notify();
}
}
@@ -420,6 +442,30 @@
}
}
+ /**
+ * Blocks until the vibration params future is complete.
+ *
+ * This should be called by the VibrationThread and may be interrupted by calling
+ * `notifyCancelled` from outside it.
+ */
+ private void waitForVibrationParamsIfRequired() {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+
+ if (mRequestVibrationParamsFuture == null) {
+ return;
+ }
+
+ try {
+ mRequestVibrationParamsFuture.orTimeout(
+ vibrationSettings.getRequestVibrationParamsTimeoutMs(),
+ TimeUnit.MILLISECONDS).get();
+ } catch (Throwable e) {
+ Slog.w(TAG, "Failed to retrieve vibration params.", e);
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasPendingNotifySignalLocked() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 9d75249..8f8fe3c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -16,11 +16,13 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationAttributes.USAGE_UNKNOWN;
@@ -32,12 +34,18 @@
import android.frameworks.vibrator.IVibratorController;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
import android.util.Slog;
-import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* Implementation of {@link IVibratorControlService} which allows the registration of
@@ -47,16 +55,25 @@
*/
public final class VibratorControlService extends IVibratorControlService.Stub {
private static final String TAG = "VibratorControlService";
+ private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
+ private static final int NO_SCALE = -1;
private final VibratorControllerHolder mVibratorControllerHolder;
private final VibrationScaler mVibrationScaler;
private final Object mLock;
+ private final int[] mRequestVibrationParamsForUsages;
+
+ @GuardedBy("mLock")
+ private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
+ @GuardedBy("mLock")
+ private IBinder mRequestVibrationParamsToken;
public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
- VibrationScaler vibrationScaler, Object lock) {
+ VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
mVibrationScaler = vibrationScaler;
mLock = lock;
+ mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
}
@Override
@@ -85,8 +102,9 @@
+ "controller doesn't match the registered one. " + this);
return;
}
- updateAdaptiveHapticsScales(/* params= */ null);
+ mVibrationScaler.clearAdaptiveHapticsScales();
mVibratorControllerHolder.setVibratorController(null);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
}
}
@@ -131,10 +149,8 @@
+ "controller doesn't match the registered one. " + this);
return;
}
- //TODO(305942827): Update this method to only clear the specified vibration types.
- // Perhaps look into whether it makes more sense to have this clear all scales and
- // rely on setVibrationParams for clearing the scales for specific vibrations.
- updateAdaptiveHapticsScales(/* params= */ null);
+
+ updateAdaptiveHapticsScales(types, NO_SCALE);
}
}
@@ -142,7 +158,26 @@
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
throws RemoteException {
- // TODO(305942827): Cache the vibration params in VibrationScaler
+ Objects.requireNonNull(requestToken);
+
+ synchronized (mLock) {
+ if (mRequestVibrationParamsToken == null) {
+ Slog.wtf(TAG,
+ "New vibration params received but no token was cached in the service. "
+ + "New vibration params ignored.");
+ return;
+ }
+
+ if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+ Slog.w(TAG,
+ "New vibration params received but the provided token does not match the "
+ + "cached one. New vibration params ignored.");
+ return;
+ }
+
+ updateAdaptiveHapticsScales(result);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
+ }
}
@Override
@@ -156,50 +191,190 @@
}
/**
- * Extracts the vibration scales and caches them in {@link VibrationScaler}.
+ * If an {@link IVibratorController} is registered to the service, it will request the latest
+ * vibration params and return a {@link CompletableFuture} that completes when the request is
+ * fulfilled. Otherwise, ignores the call and returns null.
*
- * @param params the new vibration params to cache.
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @param timeoutInMillis the request's timeout in millis.
+ * @return a {@link CompletableFuture} to track the completion of the vibration param
+ * request, or null if no {@link IVibratorController} is registered.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null || params.length == 0) {
- mVibrationScaler.updateAdaptiveHapticsScales(null);
- return;
- }
+ @Nullable
+ public CompletableFuture<Void> triggerVibrationParamsRequest(
+ @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to request vibration params. There is no registered "
+ + "IVibrationController.");
+ return null;
+ }
- SparseArray<Float> vibrationScales = new SparseArray<>();
- for (int i = 0; i < params.length; i++) {
- ScaleParam scaleParam = params[i].getScale();
- extractVibrationScales(scaleParam, vibrationScales);
+ int vibrationType = mapToAdaptiveVibrationType(usage);
+ if (vibrationType == UNRECOGNIZED_VIBRATION_TYPE) {
+ Slog.d(TAG, "Unable to request vibration params. The provided usage " + usage
+ + " is unrecognized.");
+ return null;
+ }
+
+ try {
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ mRequestVibrationParamsFuture = new CompletableFuture<>();
+ mRequestVibrationParamsToken = new Binder();
+ vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
+ mRequestVibrationParamsToken);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to request vibration params.", e);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ }
+
+ return mRequestVibrationParamsFuture;
}
- mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
}
/**
- * Extracts the vibration scales and map them to their corresponding
- * {@link android.os.VibrationAttributes} usages.
+ * If an {@link IVibratorController} is registered to the service, then it checks whether to
+ * request new vibration params before playing the vibration. Returns true if the
+ * usage is for high latency vibrations, e.g. ringtone and notification, and can be delayed
+ * slightly. Otherwise, returns false.
+ *
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @return true if usage is for high latency vibrations, false otherwise.
*/
- private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
- if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_ALARM, scaleParam.scale);
+ public boolean shouldRequestVibrationParams(@VibrationAttributes.Usage int usage) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to check if should request vibration params. "
+ + "There is no registered IVibrationController.");
+ return false;
+ }
+
+ return ArrayUtils.contains(mRequestVibrationParamsForUsages, usage);
+ }
+ }
+
+ /**
+ * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+ * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
+ */
+ @VisibleForTesting
+ public IBinder getRequestVibrationParamsToken() {
+ synchronized (mLock) {
+ return mRequestVibrationParamsToken;
+ }
+ }
+
+ /**
+ * Completes or cancels the vibration params request future and resets the future and token
+ * to null.
+ * @param wasCancelled specifies whether the future should be ended by being cancelled or not.
+ */
+ @GuardedBy("mLock")
+ private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
+ mRequestVibrationParamsToken = null;
+ if (mRequestVibrationParamsFuture == null) {
+ return;
}
- if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
- vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
+ if (wasCancelled) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
+ } else {
+ mRequestVibrationParamsFuture.complete(null);
}
- if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
+ mRequestVibrationParamsFuture = null;
+ }
+
+ private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case USAGE_ALARM -> {
+ return ScaleParam.TYPE_ALARM;
+ }
+ case USAGE_NOTIFICATION, USAGE_COMMUNICATION_REQUEST -> {
+ return ScaleParam.TYPE_NOTIFICATION;
+ }
+ case USAGE_RINGTONE -> {
+ return ScaleParam.TYPE_RINGTONE;
+ }
+ case USAGE_MEDIA, USAGE_UNKNOWN -> {
+ return ScaleParam.TYPE_MEDIA;
+ }
+ case USAGE_TOUCH, USAGE_HARDWARE_FEEDBACK, USAGE_ACCESSIBILITY,
+ USAGE_PHYSICAL_EMULATION -> {
+ return ScaleParam.TYPE_INTERACTIVE;
+ }
+ default -> {
+ Slog.w(TAG, "Unrecognized vibration usage " + usage);
+ return UNRECOGNIZED_VIBRATION_TYPE;
+ }
+ }
+ }
+
+ /**
+ * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the
+ * provided params.
+ *
+ * @param params the new vibration params.
+ */
+ private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+ for (VibrationParam param : params) {
+ ScaleParam scaleParam = param.getScale();
+ updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale);
+ }
+ }
+
+ /**
+ * Updates the adaptive haptics scales, cached in {@link VibrationScaler}, for the provided
+ * vibration types.
+ *
+ * @param types The type of vibrations.
+ * @param scale The scaling factor that should be applied to the vibrations.
+ */
+ private void updateAdaptiveHapticsScales(int types, float scale) {
+ if ((ScaleParam.TYPE_ALARM & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_ALARM, scale);
}
- if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
- vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
+ if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_NOTIFICATION, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, scale);
}
- if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
- vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
+ if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_RINGTONE, scale);
}
+
+ if ((ScaleParam.TYPE_MEDIA & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_MEDIA, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_UNKNOWN, scale);
+ }
+
+ if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_TOUCH, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_HARDWARE_FEEDBACK, scale);
+ }
+ }
+
+ /**
+ * Updates or removes the adaptive haptics scale for the specified usage. If the scale is set
+ * to {@link #NO_SCALE} then it will be removed from the cached usage scales in
+ * {@link VibrationScaler}. Otherwise, the cached usage scale will be updated by the new value.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to the vibrations. If set to
+ * {@link #NO_SCALE} then the scale will be removed.
+ */
+ private void updateOrRemoveAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint,
+ float scale) {
+ if (scale == NO_SCALE) {
+ mVibrationScaler.removeAdaptiveHapticsScale(usageHint);
+ return;
+ }
+
+ mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
index 63e69db..79a99b3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -57,7 +57,7 @@
@Override
public void binderDied(@NonNull IBinder deadBinder) {
- if (deadBinder == mVibratorController.asBinder()) {
+ if (mVibratorController != null && deadBinder == mVibratorController.asBinder()) {
setVibratorController(null);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2c1ab95..759450b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -53,6 +53,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.VibratorInfoFactory;
@@ -84,6 +85,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -161,6 +163,7 @@
private final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
+ private final VibratorControlService mVibratorControlService;
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceAdapter mDeviceAdapter;
@@ -212,6 +215,9 @@
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ mVibratorControlService = new VibratorControlService(
+ injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
+ mLock);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -272,9 +278,7 @@
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
- injector.addService(VIBRATOR_CONTROL_SERVICE,
- new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
- mLock));
+ injector.addService(VIBRATOR_CONTROL_SERVICE, mVibratorControlService);
}
}
@@ -783,19 +787,12 @@
private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!vib.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- // Scale effect before dispatching it to the input devices or the vibration thread.
- vib.scaleEffects(mVibrationScaler::scale);
- }
- boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
- vib.callerInfo, vib.getEffectToPlay());
- if (inputDevicesAvailable) {
- return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ if (mInputDeviceDelegate.isAvailable()) {
+ return startVibrationOnInputDevicesLocked(vib);
}
- VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
- mDeviceAdapter, mVibrationThreadCallbacks);
+ VibrationStepConductor conductor = createVibrationStepConductor(vib);
+
if (mCurrentVibration == null) {
return startVibrationOnThreadLocked(conductor);
}
@@ -866,6 +863,34 @@
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
+ private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+ CompletableFuture<Void> requestVibrationParamsFuture = null;
+
+ if (Flags.adaptiveHapticsEnabled() && !vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
+ && mVibratorControlService.shouldRequestVibrationParams(
+ vib.callerInfo.attrs.getUsage())) {
+ requestVibrationParamsFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(
+ vib.callerInfo.attrs.getUsage(),
+ mVibrationSettings.getRequestVibrationParamsTimeoutMs());
+ }
+
+ return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
+ requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ }
+
+ private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ if (!vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ // Scale effect before dispatching it to the input devices.
+ vib.scaleEffects(mVibrationScaler::scale);
+ }
+ mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
+
+ return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs,
Vibration.Status status) {
switch (status) {
@@ -1395,6 +1420,10 @@
void addService(String name, IBinder service) {
ServiceManager.addService(name, service);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ return new VibratorControllerHolder();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa8c35a..19ea9f9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -626,6 +626,8 @@
private boolean mForceShowMagnifiableBounds = false;
+ private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
+
DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
@@ -655,13 +657,28 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec",
FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
- mMagnifedViewport.updateMagnificationSpec(spec);
+ updateMagnificationSpec(spec);
mMagnifedViewport.recomputeBounds();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
+ void updateMagnificationSpec(MagnificationSpec spec) {
+ if (spec != null) {
+ mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+ } else {
+ mMagnificationSpec.clear();
+ }
+ // If this message is pending we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ mMagnifedViewport.setMagnifiedRegionBorderShown(
+ isForceShowingMagnifiableBounds(), true);
+ }
+ }
+
void setForceShowMagnifiableBounds(boolean show) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
@@ -800,8 +817,7 @@
case WindowManager.LayoutParams.TYPE_QS_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
Rect magnifiedRegionBounds = mTempRect2;
- mMagnifedViewport.getMagnifiedFrameInContentCoords(
- magnifiedRegionBounds);
+ getMagnifiedFrameInContentCoords(magnifiedRegionBounds);
Rect touchableRegionBounds = mTempRect1;
windowState.getTouchableRegion(mTempRegion1);
mTempRegion1.getBounds(touchableRegionBounds);
@@ -818,6 +834,14 @@
}
}
+ void getMagnifiedFrameInContentCoords(Rect rect) {
+ Region magnificationRegion = new Region();
+ mMagnifedViewport.getMagnificationRegion(magnificationRegion);
+ magnificationRegion.getBounds(rect);
+ rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
+ rect.scale(1.0f / mMagnificationSpec.scale);
+ }
+
void notifyImeWindowVisibilityChanged(boolean shown) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".notifyImeWindowVisibilityChanged",
@@ -832,13 +856,13 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow",
FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}");
}
- MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
- if (spec != null && !spec.isNop()) {
+
+ if (mMagnificationSpec != null && !mMagnificationSpec.isNop()) {
if (!windowState.shouldMagnify()) {
return null;
}
}
- return spec;
+ return mMagnificationSpec;
}
void getMagnificationRegion(Region outMagnificationRegion) {
@@ -852,6 +876,10 @@
mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
}
+ boolean isMagnifying() {
+ return mMagnificationSpec.scale > 1.0f;
+ }
+
void destroy() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
@@ -897,8 +925,6 @@
private final Path mCircularPath;
- private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
-
private final float mBorderWidth;
private final int mHalfBorderWidth;
private final int mDrawBorderInset;
@@ -932,20 +958,6 @@
outMagnificationRegion.set(mMagnificationRegion);
}
- void updateMagnificationSpec(MagnificationSpec spec) {
- if (spec != null) {
- mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
- } else {
- mMagnificationSpec.clear();
- }
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShown(isForceShowingMagnifiableBounds(), true);
- }
- }
-
void recomputeBounds() {
getDisplaySizeLocked(mScreenSize);
final int screenWidth = mScreenSize.x;
@@ -1127,21 +1139,6 @@
}
}
- void getMagnifiedFrameInContentCoords(Rect rect) {
- MagnificationSpec spec = mMagnificationSpec;
- mMagnificationRegion.getBounds(rect);
- rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
- rect.scale(1.0f / spec.scale);
- }
-
- boolean isMagnifying() {
- return mMagnificationSpec.scale > 1.0f;
- }
-
- MagnificationSpec getMagnificationSpec() {
- return mMagnificationSpec;
- }
-
void drawWindowIfNeeded() {
recomputeBounds();
mWindow.postDrawIfNeeded();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 2e0546e..1128d0c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,9 +1319,33 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ if (!controller.isShellTransitionsEnabled()) {
r.setShowWhenLocked(showWhenLocked);
+ return;
}
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
+ r.setShowWhenLocked(showWhenLocked);
+ return;
+ }
+ final Transition transition = new Transition(
+ showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setShowWhenLocked(showWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1334,9 +1358,34 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ // If shell transitions is not enabled just set it directly.
+ if (!controller.isShellTransitionsEnabled()) {
r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
}
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
+ }
+ final Transition transition = new Transition(
+ inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 03d55d9..b1d04c9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -545,9 +545,6 @@
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean delayedResume; // not yet resumed because of stopped app switches?
boolean finishing; // activity in pending finish list?
- boolean deferRelaunchUntilPaused; // relaunch of activity is being deferred until pause is
- // completed
- boolean preserveWindowOnDeferredRelaunch; // activity windows are preserved on deferred relaunch
int configChangeFlags; // which config values have changed
private boolean keysPaused; // has key dispatching been paused for it?
int launchMode; // the launch mode activity attribute.
@@ -1277,10 +1274,8 @@
if (mDeferHidingClient) {
pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient);
}
- if (deferRelaunchUntilPaused || configChangeFlags != 0) {
- pw.print(prefix); pw.print("deferRelaunchUntilPaused=");
- pw.print(deferRelaunchUntilPaused);
- pw.print(" configChangeFlags=");
+ if (configChangeFlags != 0) {
+ pw.print(prefix); pw.print(" configChangeFlags=");
pw.println(Integer.toHexString(configChangeFlags));
}
if (mServiceConnectionsHolder != null) {
@@ -2137,7 +2132,6 @@
launchFailed = false;
delayedResume = false;
finishing = false;
- deferRelaunchUntilPaused = false;
keysPaused = false;
inHistory = false;
nowVisible = false;
@@ -4096,8 +4090,6 @@
// Clean up the splash screen if it was still displayed.
cleanUpSplashScreen();
- deferRelaunchUntilPaused = false;
-
if (setState) {
setState(DESTROYED, "cleanUp");
if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this);
@@ -6481,9 +6473,6 @@
mAppStopped = true;
ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this);
setState(STOPPED, "stopIfPossible");
- if (deferRelaunchUntilPaused) {
- destroyImmediately("stop-except");
- }
}
}
@@ -6539,12 +6528,7 @@
if (finishing) {
abortAndClearOptionsAnimation();
} else {
- if (deferRelaunchUntilPaused) {
- destroyImmediately("stop-config");
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- } else {
- mAtmService.updatePreviousProcess(this);
- }
+ mAtmService.updatePreviousProcess(this);
}
mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
}
@@ -9724,23 +9708,12 @@
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
- if (mState == PAUSING) {
- // A little annoying: we are waiting for this activity to finish pausing. Let's not
- // do anything now, but just flag that it needs to be restarted when done pausing.
- ProtoLog.v(WM_DEBUG_CONFIGURATION,
- "Config is skipping already pausing %s", this);
- deferRelaunchUntilPaused = true;
- preserveWindowOnDeferredRelaunch = preserveWindow;
- return true;
- } else {
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
- this);
- if (!mVisibleRequested) {
- ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
- + "activity %s called by %s", this, Debug.getCallers(4));
- }
- relaunchActivityLocked(preserveWindow);
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s", this);
+ if (!mVisibleRequested) {
+ ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
+ + "activity %s called by %s", this, Debug.getCallers(4));
}
+ relaunchActivityLocked(preserveWindow);
// All done... tell the caller we weren't able to keep this activity around.
return false;
@@ -9958,8 +9931,6 @@
mTaskSupervisor.mStoppingActivities.remove(this);
configChangeFlags = 0;
- deferRelaunchUntilPaused = false;
- preserveWindowOnDeferredRelaunch = false;
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6f52b8..85580ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1034,20 +1034,19 @@
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+ // We couldn't find the specific class specified in the Intent.
+ err = ActivityManager.START_CLASS_NOT_FOUND;
+
if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
- return packageArchiver
+ err = packageArchiver
.requestUnarchiveOnActivityStart(
intent, callingPackage, mRequest.userId, realCallingUid);
}
}
-
- // We couldn't find the specific class specified in the Intent.
- // Also the end of the line.
- err = ActivityManager.START_CLASS_NOT_FOUND;
}
if (err == ActivityManager.START_SUCCESS && sourceRecord != null
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 83ccbdc..13f6a5f 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1527,6 +1527,12 @@
setLaunchBehind(visibleOpenActivities[i]);
}
}
+ // Force update mLastSurfaceShowing for opening activity and its task.
+ if (mWindowManagerService.mRoot.mTransitionController.isShellTransitionsEnabled()) {
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ WindowContainer.enforceSurfaceVisible(visibleOpenActivities[i]);
+ }
+ }
}
@Nullable Runnable build() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e2bc59b..a7bbc25 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1913,12 +1913,9 @@
*/
final Rect mConfigFrame = new Rect();
- /** The count of insets sources when calculating this info. */
- int mLastInsetsSourceCount;
-
private boolean mNeedUpdate = true;
- void update(DisplayContent dc, int rotation, int w, int h) {
+ InsetsState update(DisplayContent dc, int rotation, int w, int h) {
final DisplayFrames df = new DisplayFrames();
dc.updateDisplayFrames(df, rotation, w, h);
dc.getDisplayPolicy().simulateLayoutDisplay(df);
@@ -1935,8 +1932,8 @@
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
- mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size();
mNeedUpdate = false;
+ return insetsState;
}
void set(Info other) {
@@ -1944,7 +1941,6 @@
mConfigInsets.set(other.mConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
- mLastInsetsSourceCount = other.mLastInsetsSourceCount;
mNeedUpdate = false;
}
@@ -1997,6 +1993,29 @@
}
}
+ static boolean hasInsetsFrameDiff(InsetsState s1, InsetsState s2, int insetsTypes) {
+ int insetsCount1 = 0;
+ for (int i = s1.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source1 = s1.sourceAt(i);
+ if ((source1.getType() & insetsTypes) == 0) {
+ continue;
+ }
+ insetsCount1++;
+ final InsetsSource source2 = s2.peekSource(source1.getId());
+ if (source2 == null || !source2.getFrame().equals(source1.getFrame())) {
+ return true;
+ }
+ }
+ int insetsCount2 = 0;
+ for (int i = s2.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source2 = s2.sourceAt(i);
+ if ((source2.getType() & insetsTypes) != 0) {
+ insetsCount2++;
+ }
+ }
+ return insetsCount1 != insetsCount2;
+ }
+
private static class Cache {
/**
* If {@link #mPreserveId} is this value, it is in the middle of updating display
@@ -2031,12 +2050,14 @@
final int dw = displayFrames.mWidth;
final int dh = displayFrames.mHeight;
final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
- newInfo.update(mDisplayContent, rotation, dw, dh);
+ final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
- // insets in other rotations if the source count is changed.
- if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) {
+ // insets in other rotations if the frame of insets source is changed.
+ final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
+ if (DecorInsets.hasInsetsFrameDiff(
+ newInsetsState, currentInsetsState, mService.mConfigTypes)) {
for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) {
if (i != rotation) {
final boolean flipSize = (i + rotation) % 2 == 1;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 7b23004..7a3124d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -160,7 +160,8 @@
// Invisible activity should be stopped. If the recents activity is alive and its doesn't
// need to relaunch by current configuration, then it may be already in stopped state.
- if (!targetActivity.isState(STOPPING, STOPPED)) {
+ if (!targetActivity.finishing && targetActivity.isAttached()
+ && !targetActivity.isState(STOPPING, STOPPED)) {
// Add to stopping instead of stop immediately. So the client has the chance to perform
// traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
// things (e.g. the measure can be done earlier). The actual stop will be performed when
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 11e7bb0..838ce86 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1915,11 +1915,7 @@
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,
prev.isVisibleRequested());
- if (prev.deferRelaunchUntilPaused) {
- // Complete the deferred relaunch that was waiting for pause to complete.
- ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
- prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
- } else if (wasStopping) {
+ if (wasStopping) {
// We are also stopping, the stop request must have gone soon after the pause.
// We can't clobber it, because the stop confirmation will not be handled.
// We don't need to schedule another stop, we only need to let it happen.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2accf9a..1e58306 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,6 +2917,26 @@
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
}
+ /**
+ * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
+ * only here for legacy usages where we can't tell ahead-of-time whether something will
+ * generate a change.
+ */
+ boolean isNoOp() {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ // This is the same criteria as the rejection logic in calculateTargets
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ if (!wc.isAttached()) continue;
+ // The level of transition target should be at least window token.
+ if (wc.asWindowState() != null) continue;
+ final ChangeInfo changeInfo = mChanges.get(wc);
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) continue;
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25b5630f..70775530 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -994,39 +994,18 @@
Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying
+ " caller=" + caller);
if (!isPlaying) {
- enforceSurfaceVisible(wc);
+ WindowContainer.enforceSurfaceVisible(wc);
return;
}
// Update surface visibility after the playing transitions are finished, so the last
// visibility won't be replaced by the finish transaction of transition.
mStateValidators.add(() -> {
if (wc.isVisibleRequested()) {
- enforceSurfaceVisible(wc);
+ WindowContainer.enforceSurfaceVisible(wc);
}
});
}
- private void enforceSurfaceVisible(WindowContainer<?> wc) {
- if (wc.mSurfaceControl == null) return;
- wc.getSyncTransaction().show(wc.mSurfaceControl);
- final ActivityRecord ar = wc.asActivityRecord();
- if (ar != null) {
- ar.mLastSurfaceShowing = true;
- }
- // Force showing the parents because they may be hidden by previous transition.
- for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
- p = p.getParent()) {
- if (p.mSurfaceControl != null) {
- p.getSyncTransaction().show(p.mSurfaceControl);
- final Task task = p.asTask();
- if (task != null) {
- task.mLastSurfaceShowing = true;
- }
- }
- }
- wc.scheduleAnimation();
- }
-
/**
* Called when the transition has a complete set of participants for its operation. In other
* words, it is when the transition is "ready" but is still waiting for participants to draw.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 286182e..2d2857a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3625,6 +3625,29 @@
return mSurfaceControl.getHeight();
}
+ static void enforceSurfaceVisible(@NonNull WindowContainer<?> wc) {
+ if (wc.mSurfaceControl == null) {
+ return;
+ }
+ wc.getSyncTransaction().show(wc.mSurfaceControl);
+ final ActivityRecord ar = wc.asActivityRecord();
+ if (ar != null) {
+ ar.mLastSurfaceShowing = true;
+ }
+ // Force showing the parents because they may be hidden by previous transition.
+ for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
+ p = p.getParent()) {
+ if (p.mSurfaceControl != null) {
+ p.getSyncTransaction().show(p.mSurfaceControl);
+ final Task task = p.asTask();
+ if (task != null) {
+ task.mLastSurfaceShowing = true;
+ }
+ }
+ }
+ wc.scheduleAnimation();
+ }
+
@CallSuper
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
if (mSurfaceAnimator.isAnimating()) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3f889c0..8cd399f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1471,12 +1471,11 @@
final int index = task.mChildren.indexOf(topTaskFragment);
task.mChildren.remove(taskFragment);
task.mChildren.add(index, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
break;
@@ -1491,12 +1490,11 @@
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(0, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
@@ -1505,12 +1503,11 @@
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index cbc301b..4a6b31c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -290,6 +290,7 @@
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
+ void setTouchpadTapDraggingEnabled(bool enabled);
void setTouchpadRightClickZoneEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
@@ -440,6 +441,9 @@
// True to enable tap-to-click on touchpads.
bool touchpadTapToClickEnabled{true};
+ // True to enable tap dragging on touchpads.
+ bool touchpadTapDraggingEnabled{false};
+
// True to enable a zone on the right-hand side of touchpads where clicks will be turned
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
@@ -697,6 +701,7 @@
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
+ outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1301,6 +1306,22 @@
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadTapDraggingEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadTapDraggingEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad tap dragging to %s.", toString(enabled));
+ mLocked.touchpadTapDraggingEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -2223,6 +2244,13 @@
im->setTouchpadTapToClickEnabled(enabled);
}
+static void nativeSetTouchpadTapDraggingEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadTapDraggingEnabled(enabled);
+}
+
static void nativeSetTouchpadRightClickZoneEnabled(JNIEnv* env, jobject nativeImplObj,
jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2844,6 +2872,7 @@
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
(void*)nativeSetTouchpadNaturalScrollingEnabled},
{"setTouchpadTapToClickEnabled", "(Z)V", (void*)nativeSetTouchpadTapToClickEnabled},
+ {"setTouchpadTapDraggingEnabled", "(Z)V", (void*)nativeSetTouchpadTapDraggingEnabled},
{"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setInteractive", "(Z)V", (void*)nativeSetInteractive},
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962..c18a9e5 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -1,8 +1,4 @@
# Bug component: 554432
-include /services/core/java/com/android/server/pm/OWNERS
+include /PACKAGE_MANAGER_OWNERS
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
-zyy@google.com
-patb@google.com
+zyy@google.com
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 8f464d4..fc2eb26 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
import android.util.ArrayMap
@@ -213,7 +214,10 @@
val uid = key.first
val appOpCode = key.second
- listener.onUidModeChanged(uid, appOpCode, mode)
+ listener.onUidModeChanged(uid,
+ appOpCode,
+ mode,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index edacda0..15c9b9f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -20,7 +20,6 @@
import android.os.Build
import android.util.Slog
import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.hasAnyBit
@@ -61,10 +60,11 @@
if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) {
Slog.v(
LOG_TAG,
- "Upgrading scoped permissions for package: $packageName" +
+ "Upgrading scoped media and body sensor permissions for package: $packageName" +
", version: $version, user: $userId"
)
upgradeAuralVisualMediaPermissions(packageState, userId)
+ upgradeBodySensorPermissions(packageState, userId)
}
// TODO Enable isAtLeastU check, when moving subsystem to mainline.
if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) {
@@ -182,6 +182,50 @@
}
}
+ private fun MutateStateScope.upgradeBodySensorPermissions(
+ packageState: PackageState,
+ userId: Int
+ ) {
+ if (
+ Manifest.permission.BODY_SENSORS_BACKGROUND !in
+ packageState.androidPackage!!.requestedPermissions
+ ) {
+ return
+ }
+
+ // Should have been granted when first getting exempt as if the perm was just split
+ val appId = packageState.appId
+ val backgroundBodySensorsFlags =
+ with(policy) {
+ getPermissionFlags(appId, userId, Manifest.permission.BODY_SENSORS_BACKGROUND)
+ }
+ if (backgroundBodySensorsFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)) {
+ return
+ }
+
+ // Add Upgrade Exemption - BODY_SENSORS_BACKGROUND is a restricted permission
+ with(policy) {
+ updatePermissionFlags(
+ appId,
+ userId,
+ Manifest.permission.BODY_SENSORS_BACKGROUND,
+ PermissionFlags.UPGRADE_EXEMPT,
+ PermissionFlags.UPGRADE_EXEMPT,
+ )
+ }
+
+ val bodySensorsFlags =
+ with(policy) { getPermissionFlags(appId, userId, Manifest.permission.BODY_SENSORS) }
+ val isForegroundBodySensorsGranted = PermissionFlags.isAppOpGranted(bodySensorsFlags)
+ if (isForegroundBodySensorsGranted) {
+ grantRuntimePermission(
+ packageState,
+ userId,
+ Manifest.permission.BODY_SENSORS_BACKGROUND
+ )
+ }
+ }
+
/** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */
private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission(
packageState: PackageState,
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
index b380e39..be9e61f 100644
--- a/services/profcollect/OWNERS
+++ b/services/profcollect/OWNERS
@@ -1,3 +1 @@
-srhines@google.com
-yabinc@google.com
-yikong@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 582b712..fb0fbe8 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -62,7 +62,7 @@
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private static final String INTENT_UPLOAD_PROFILES =
"com.android.server.profcollect.UPLOAD_PROFILES";
- private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
+ private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours.
private IProfCollectd mIProfcollect;
private static ProfcollectForwardingService sSelfService;
@@ -226,7 +226,7 @@
js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(BG_PROCESS_PERIOD)
+ .setPeriodic(BG_PROCESS_INTERVAL)
.setPriority(JobInfo.PRIORITY_MIN)
.build());
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 321d945..d928306 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -46,7 +46,6 @@
"androidx.test.espresso.core",
"androidx.test.espresso.contrib",
"androidx.test.ext.truth",
- "backup_flags_lib",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 47ae97f..0e85626 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -44,6 +44,7 @@
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -309,7 +310,8 @@
mRestrictedPackages.remove(p);
}
if (mAppOpsCallback != null) {
- mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+ mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index b39cd04..a476155 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -125,6 +125,7 @@
import android.app.compat.CompatChanges;
import android.app.tare.EconomyManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -2980,7 +2981,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID,
TEST_CALLING_PACKAGE, true);
@@ -2993,7 +2995,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
verify(mService.mHandler, never()).sendMessageAtTime(
argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
@@ -3008,7 +3011,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index a65ef00..bf00b75 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -552,20 +552,22 @@
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
- mIcon);
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isEqualTo(mIcon);
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e22d99d..5611415 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -84,6 +84,7 @@
"flag-junit",
"ravenwood-junit",
"net_flags_lib",
+ "CtsVirtualDeviceCommonLib",
],
libs: [
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index a2aaccc..b487dc6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -31,17 +33,23 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpActiveChangedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests app ops version upgrades
@@ -50,6 +58,8 @@
@RunWith(AndroidJUnit4.class)
public class AppOpsActiveWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -69,7 +79,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- isNull(), eq(true), anyInt(), anyInt());
+ isNull(), eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// This should be the only callback we got
verifyNoMoreInteractions(listener);
@@ -88,7 +98,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(false), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(false), anyInt(), anyInt());
// Verify that the op is not active
assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA,
@@ -126,7 +136,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(true), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// Finish up
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
@@ -134,6 +144,64 @@
}
@Test
+ public void testWatchActiveOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpActiveChangedListener listener = mock(OnOpActiveChangedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingActive(new String[]{AppOpsManager.OPSTR_CAMERA,
+ AppOpsManager.OPSTR_RECORD_AUDIO}, getContext().getMainExecutor(), listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(true),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(false),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingActive(listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
public void testIsRunning() throws Exception {
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
// Start the op
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index b5229d8..1abd4eb 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,27 +22,36 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Tests watching noted ops.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
-
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -66,12 +75,14 @@
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Stop watching
@@ -96,13 +107,54 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Finish up
appOpsManager.stopWatchingNoted(listener);
}
+ @Test
+ public void testWatchNotedOpsForExternalDevice() {
+ final AppOpsManager.OnOpNotedListener listener = mock(
+ AppOpsManager.OnOpNotedListener.class);
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, attributionSource, "message");
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingNoted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index e98a4dd..2890078 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -25,22 +27,31 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -63,15 +74,17 @@
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
@@ -97,8 +110,9 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener);
@@ -109,6 +123,50 @@
appOpsManager.stopWatchingStarted(listener);
}
+ @Test
+ public void testWatchStartedOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpStartedListener listener = mock(OnOpStartedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource, false,
+ "message", 0, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF),
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingStarted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 3a9c0f0..a1f0dbd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -70,7 +70,7 @@
mClientMonitor.binderDied();
assertThat(mClientMonitor.mCanceled).isTrue();
- assertThat(mClientMonitor.getListener()).isNull();
+ assertThat(mClientMonitor.getListener()).isNotNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
index c8bfaa9..5b81277 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -87,14 +87,6 @@
verify(mCallback).onClientFinished(mClient, true);
}
- @Test
- public void generateChallenge_nullListener() {
- createClient(null);
- mClient.start(mCallback);
-
- verify(mCallback).onClientFinished(mClient, false);
- }
-
private void createClient(ClientMonitorCallbackConverter listener) {
mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index 07dd59d2..a4628ee 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -18,6 +18,8 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.content.Context.DEVICE_ID_INVALID;
@@ -135,4 +137,34 @@
when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_SENSORS)).thenReturn(DEVICE_POLICY_CUSTOM);
assertThat(virtualDevice.hasCustomSensorSupport()).isTrue();
}
+
+ @Test
+ public void virtualDevice_hasCustomAudioInputSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isTrue();
+ }
+
+ @Test
+ public void virtualDevice_hasCustomCameraSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isTrue();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 3de167e..dacff4c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.graphics.FontListParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontUpdateRequest;
@@ -324,15 +323,16 @@
Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
FontConfig.Font fooFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(0), "foo.ttf"), null, "foo",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.Font barFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(1), "bar.ttf"), null, "bar",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
Arrays.asList(fooFont, barFont), null,
- FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(),
Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
@@ -492,10 +492,9 @@
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
- 0, null, null);
+ 0, null, null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(
Collections.emptyList(),
Collections.emptyList(),
@@ -647,10 +646,9 @@
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
- null);
+ null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
Collections.singletonList(family), "sans-serif")),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 1172a87..4641802 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -19,6 +19,7 @@
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
/** Tests for {@link OneTouchPlayAction} */
@SmallTest
@@ -70,6 +72,7 @@
private Context mContextSpy;
private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
private FakeHdmiCecConfig mHdmiCecConfig;
@@ -108,10 +111,10 @@
mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
setHdmiControlEnabled(hdmiControlEnabled);
mNativeWrapper = new FakeNativeWrapper();
- HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
- mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -618,6 +621,53 @@
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
+ @Test
+ public void onWakeUp_notInteractive_startOneTouchPlay() throws Exception {
+ setUp(true);
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ }
+
+
+ @Test
+ public void onWakeUp_interruptedByOnStandby_notInteractive_OneTouchPlayNotStarted()
+ throws Exception {
+ setUp(true);
+ long allocationDelay = TimeUnit.SECONDS.toMillis(1);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 0805485..81df597 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -157,6 +157,7 @@
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
case Context.ROLE_SERVICE:
+ case Context.APP_OPS_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
return getTestContext().getSystemService(name);
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 398148f..2e5feff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -273,7 +273,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final IAppOpsCallback watcher = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) {
latch.countDown();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9c2cba8..ef0ac33 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -227,8 +227,6 @@
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.platform.test.rule.DeniedDevices;
-import android.platform.test.rule.DeviceProduct;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -336,7 +334,6 @@
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
-@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -593,7 +590,7 @@
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
+ when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -881,9 +878,7 @@
private void simulatePackageRemovedBroadcast(String pkg, int uid) {
// mimics receive broadcast that package is removed, but doesn't remove the package.
final Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- new String[]{pkg});
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+ extras.putInt(Intent.EXTRA_UID, uid);
final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
intent.setData(Uri.parse("package:" + pkg));
@@ -1031,7 +1026,7 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
long postTime) {
- final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0);
+ final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, mUserId);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -1766,7 +1761,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotification_appBlocked", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
verify(mWorkerHandler, never()).post(
any(NotificationManagerService.EnqueueNotificationRunnable.class));
@@ -1776,7 +1771,7 @@
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
@@ -1787,7 +1782,7 @@
public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(1, mNotificationRecordLogger.numCalls());
@@ -1828,12 +1823,12 @@
Notification original = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, mUserId);
Notification update = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setCategory(Notification.CATEGORY_ALARM).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
@@ -1853,9 +1848,9 @@
public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertTrue(mNotificationRecordLogger.get(0).wasLogged);
@@ -1869,10 +1864,10 @@
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(),
- 0);
+ mUserId);
final Notification notif = generateNotificationRecord(null).getNotification();
notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertEquals(NOTIFICATION_POSTED, mNotificationRecordLogger.event(0));
@@ -1885,11 +1880,11 @@
Notification notification = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
- mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, mUserId);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
assertEquals(3, mNotificationRecordLogger.numCalls());
@@ -1949,7 +1944,7 @@
.build();
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1970,7 +1965,7 @@
n.actions[0] = null;
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1982,7 +1977,7 @@
public void enqueueNotificationWithTag_usesAndFinishesTracker() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers).hasSize(1);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers.get(0).isOngoing()).isTrue();
@@ -2000,7 +1995,7 @@
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
waitForIdle();
@@ -2017,7 +2012,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2032,7 +2027,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2044,7 +2039,7 @@
public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2062,7 +2057,7 @@
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2077,7 +2072,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2098,7 +2093,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
- notif, 0);
+ notif, mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2123,7 +2118,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
InOrder inOrder = inOrder(mPowerManager, wakeLock);
@@ -2137,7 +2132,7 @@
@Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNonexistentNotification", 0, 0);
+ "testCancelNonexistentNotification", 0, mUserId);
waitForIdle();
// The notification record logger doesn't even get called when a nonexistent notification
// is cancelled, because that happens very frequently and is not interesting.
@@ -2148,9 +2143,9 @@
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationImmediatelyAfterEnqueue", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationImmediatelyAfterEnqueue", 0, 0);
+ "testCancelNotificationImmediatelyAfterEnqueue", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -2185,13 +2180,13 @@
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationWhilePostedAndEnqueued", 0, 0);
+ "testCancelNotificationWhilePostedAndEnqueued", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -3406,12 +3401,12 @@
@Test
public void testPostNotification_appPermissionFixed() throws Exception {
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true);
+ when(mPermissionHelper.isPermissionFixed(PKG, mUserId)).thenReturn(true);
NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
+ temp.getNotification(), mUserId);
waitForIdle();
assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
StatusBarNotification[] notifs =
@@ -3443,7 +3438,7 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
- generateNotificationRecord(null, tv).getNotification(), 0);
+ generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@@ -3458,7 +3453,7 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
- 0, generateNotificationRecord(null, tv).getNotification(), 0);
+ 0, generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
anyBoolean(), anyBoolean());
@@ -11859,10 +11854,10 @@
@Test
public void testGetActiveNotification_filtersUsers() throws Exception {
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0, 10});
+ when(mUm.getProfileIds(mUserId, false)).thenReturn(new int[]{mUserId, 10});
NotificationRecord nr0 =
- generateNotificationRecord(mTestNotificationChannel, 0);
+ generateNotificationRecord(mTestNotificationChannel, mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
nr0.getSbn().getId(), nr0.getSbn().getNotification(), nr0.getSbn().getUserId());
@@ -12316,7 +12311,7 @@
.setFullScreenIntent(mock(PendingIntent.class), true)
.build();
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+ mService.fixNotification(n, PKG, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 87e822c..5d114f4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2123,6 +2123,25 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testDefaultRulesFromConfig_modesApi_getPolicies() {
+ // After mZenModeHelper was created, set some things in the policy so it's changed from
+ // default.
+ setupZenConfig();
+
+ // Find default rules; check they have non-null policies; check that they match the default
+ // and not whatever has been set up in setupZenConfig.
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ assertThat(rules).containsKey(defaultId);
+ ZenRule rule = rules.get(defaultId);
+ assertThat(rule.zenPolicy).isNotNull();
+
+ assertThat(rule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+ }
+ }
+
+ @Test
public void testAddAutomaticZenRule_beyondSystemLimit() {
for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
ScheduleInfo si = new ScheduleInfo();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index f9fe6a9..b431888 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -53,7 +53,6 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -270,10 +269,8 @@
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
- SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
- adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
- adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
- mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
@@ -287,6 +284,50 @@
assertTrue(scaled.getAmplitude() < 0.5);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_clearAdaptiveHapticsScales_clearsAllCachedScales() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.clearAdaptiveHapticsScales();
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_removeAdaptiveHapticsScale_removesCachedScale() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales down.
+ assertTrue(scaled.getAmplitude() < 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index b0aef47..6e478d8 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -54,12 +55,16 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -81,6 +86,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -95,6 +101,9 @@
private static final int TEST_RAMP_STEP_DURATION = 5;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@@ -104,6 +113,7 @@
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
+ private VibrationScaler mVibrationScaler;
private TestLooper mTestLooper;
private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
private VibrationThread mThread;
@@ -132,6 +142,7 @@
Context context = InstrumentationRegistry.getContext();
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
mVibrationConfigMock);
+ mVibrationScaler = new VibrationScaler(context, mVibrationSettings);
mockVibrators(VIBRATOR_ID);
@@ -231,6 +242,45 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+ CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.runAsync(() -> {
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ });
+ long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
+ USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
+ for (int i = 0; i < amplitudes.size(); i++) {
+ assertTrue(amplitudes.get(i) < 1 / 255f);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+
+ CompletableFuture<Void> neverCompletingFuture = new CompletableFuture<>();
+ long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ assertEquals(expectedAmplitudes(1, 1, 1),
+ mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ }
+
+ @Test
public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1610,10 +1660,26 @@
}
private long startThreadAndDispatcher(HalVibration vib) {
+ return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null);
+ }
+
+ private long startThreadAndDispatcher(VibrationEffect effect,
+ CompletableFuture<Void> requestVibrationParamsFuture, int usage) {
+ VibrationAttributes attrs = new VibrationAttributes.Builder()
+ .setUsage(usage)
+ .build();
+ HalVibration vib = new HalVibration(mVibrationToken,
+ CombinedVibration.createParallel(effect),
+ new Vibration.CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
+ }
+
+ private long startThreadAndDispatcher(HalVibration vib,
+ CompletableFuture<Void> requestVibrationParamsFuture) {
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
- mVibrationConductor =
- new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter, mManagerHooks);
+ mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
+ mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
return mVibrationConductor.getVibration().id;
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 1e0b1df..2823223 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -18,29 +18,47 @@
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.TestLooper;
import android.util.SparseArray;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
public class VibratorControlServiceTest {
@@ -49,35 +67,45 @@
@Mock
private VibrationScaler mMockVibrationScaler;
- @Captor
- private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternalMock;
+ private FakeVibratorController mFakeVibratorController;
private VibratorControlService mVibratorControlService;
+ private VibrationSettings mVibrationSettings;
private final Object mLock = new Object();
@Before
public void setUp() throws Exception {
+ when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+ .thenReturn(new ComponentName("", ""));
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+ TestLooper testLooper = new TestLooper();
+ mVibrationSettings = new VibrationSettings(
+ ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
+
+ mFakeVibratorController = new FakeVibratorController();
mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
- mMockVibrationScaler, mLock);
+ mMockVibrationScaler, mVibrationSettings, mLock);
}
@Test
public void testRegisterVibratorController() throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
- assertThat(fakeController.isLinkedToDeath).isTrue();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
}
@Test
public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.unregisterVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ mVibratorControlService.unregisterVibratorController(mFakeVibratorController);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
- assertThat(fakeController.isLinkedToDeath).isFalse();
+ verify(mMockVibrationScaler).clearAdaptiveHapticsScales();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
}
@Test
@@ -93,41 +121,80 @@
}
@Test
+ public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly()
+ throws RemoteException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ generateVibrationParams(vibrationScales));
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
+ // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
+
+ assertThat(future.isDone()).isTrue();
+ assertThat(future.isCompletedExceptionally()).isFalse();
+ }
+
+ @Test
+ public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest()
+ throws RemoteException, InterruptedException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
+ generateVibrationParams(vibrationScales));
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ @Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
+ mFakeVibratorController);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
- SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
- assertThat(cachedVibrationScales.size()).isEqualTo(3);
- assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
- assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
- assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
- assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
// notification and communication request usages.
- assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
- assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test
public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -135,23 +202,72 @@
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int types = buildVibrationTypesMask(ScaleParam.TYPE_ALARM, ScaleParam.TYPE_NOTIFICATION);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+ mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
+
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+ // Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST);
}
@Test
public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test
+ public void testRequestVibrationParams_createsFutureRequestProperly()
+ throws RemoteException {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ try {
+ future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
+ } catch (Throwable ignored) {
+ }
+ assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
+ ScaleParam.TYPE_RINGTONE);
+ assertThat(mFakeVibratorController.requestTimeoutInMillis).isEqualTo(timeoutInMillis);
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration))
+ .isEqualTo(ArrayUtils.contains(
+ mVibrationSettings.getRequestVibrationParamsForUsages(), vibration));
+ }
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration)).isFalse();
+ }
+ }
+
private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
List<VibrationParam> vibrationParamList = new ArrayList<>();
for (int i = 0; i < vibrationScales.size(); i++) {
@@ -173,4 +289,12 @@
return vibrationParam;
}
+
+ private int buildVibrationTypesMask(int... types) {
+ int typesMask = 0;
+ for (int type : types) {
+ typesMask |= type;
+ }
+ return typesMask;
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index d6b2116..bdbb6c6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -318,6 +318,12 @@
return new HapticFeedbackVibrationProvider(
resources, vibratorInfo, mHapticFeedbackVibrationMap);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ VibratorControllerHolder holder = new VibratorControllerHolder();
+ holder.setVibratorController(new FakeVibratorController());
+ return holder;
+ }
});
return mService;
}
@@ -965,8 +971,9 @@
new long[]{10, 10_000}, new int[]{255, 0}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
ALARM_ATTRS);
@@ -1019,8 +1026,9 @@
new long[]{10, 10_000}, new int[]{255, 0}, -1);
vibrate(service, alarmEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
UNKNOWN_ATTRS);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 7e23587..3912206 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -20,6 +20,7 @@
import android.frameworks.vibrator.IVibratorController;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
/**
* Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
@@ -28,10 +29,16 @@
public final class FakeVibratorController extends IVibratorController.Stub {
public boolean isLinkedToDeath = false;
+ public boolean didRequestVibrationParams = false;
+ public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
+ public long requestTimeoutInMillis = 0;
@Override
- public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
-
+ public void requestVibrationParams(int vibrationType, long timeoutInMillis, IBinder iBinder)
+ throws RemoteException {
+ didRequestVibrationParams = true;
+ requestVibrationType = vibrationType;
+ requestTimeoutInMillis = timeoutInMillis;
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0382ca0..e21388e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -34,6 +34,7 @@
import static android.view.KeyEvent.KEYCODE_SLASH;
import static android.view.KeyEvent.KEYCODE_SPACE;
import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_SCREENSHOT;
import static android.view.KeyEvent.KEYCODE_U;
import static android.view.KeyEvent.KEYCODE_Z;
@@ -193,4 +194,26 @@
mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
}
}
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
+ */
+ @Test
+ public void testTakeScreenshot_flagEnabled() {
+ mSetFlagsRule.enableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
+ */
+ @Test
+ public void testTakeScreenshot_flagDisabled() {
+ mSetFlagsRule.disableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotNotCalled();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 157d162..fee6582 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,6 +46,7 @@
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -57,11 +58,17 @@
import org.junit.After;
import org.junit.Rule;
+import org.junit.rules.RuleChain;
import java.util.Map;
class ShortcutKeyTestBase {
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Rule
+ public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
TestPhoneWindowManager mPhoneWindowManager;
DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index c8abd8d..2904c03 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -596,6 +596,11 @@
verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
+ void assertTakeScreenshotNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mDisplayPolicy, never()).takeScreenshot(anyInt(), anyInt());
+ }
+
void assertShowGlobalActionsCalled() {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 9e00f92..5d14334 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -417,6 +417,8 @@
di.logicalWidth, di.logicalHeight).mConfigInsets.top);
}
+ // Flush the pending change (DecorInsets.Info#mNeedUpdate) for the rotation to be tested.
+ displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90, di.logicalHeight, di.logicalWidth);
// Add a window that provides the same insets in current rotation. But it specifies
// different insets in other rotations.
final WindowState bar2 = createWindow(null, navbar.mAttrs.type, "bar2");
@@ -446,6 +448,12 @@
// The insets in other rotations should be still updated.
assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90,
di.logicalHeight, di.logicalWidth).mConfigInsets.bottom);
+ // Restore to previous height and the insets can still be updated.
+ bar2.mAttrs.paramsForRotation[Surface.ROTATION_90].providedInsets[0].setInsetsSize(
+ Insets.of(0, 0, 0, NAV_BAR_HEIGHT));
+ assertFalse(displayPolicy.updateDecorInsetsInfo());
+ assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90,
+ di.logicalHeight, di.logicalWidth).mConfigInsets.bottom);
navbar.removeIfPossible();
bar2.removeIfPossible();
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 3b4b220..9930c88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -1207,6 +1208,29 @@
}
@Test
+ public void addTask_tasksAreAddedAccordingToZOrder() {
+ final Task firstTask = new TaskBuilder(mSupervisor).setTaskId(1)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final Task secondTask = new TaskBuilder(mSupervisor).setTaskId(2)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+
+ assertEquals(-1, firstTask.compareTo(secondTask));
+
+ // initial addition when tasks are created
+ mRecentTasks.add(firstTask);
+ mRecentTasks.add(secondTask);
+
+ assertRecentTasksOrder(secondTask, firstTask);
+
+ // Tasks are added in a different order
+ mRecentTasks.add(secondTask);
+ mRecentTasks.add(firstTask);
+
+ // order in recents don't change as first task has lower z-order
+ assertRecentTasksOrder(secondTask, firstTask);
+ }
+
+ @Test
public void removeTask_callsTaskNotificationController() {
final Task task = createTaskBuilder(".Task").build();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c1ceaef..89661a4 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6638,11 +6638,7 @@
}
}
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- private ITelephony getITelephony() {
+ private static ITelephony getITelephony() {
// Keeps cache disabled until test fixes are checked into AOSP.
if (!sServiceHandleCacheEnabled) {
return ITelephony.Stub.asInterface(
@@ -18696,11 +18692,7 @@
@SimState
public static int getSimStateForSlotIndex(int slotIndex) {
try {
- ITelephony telephony = ITelephony.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getTelephonyServiceRegisterer()
- .get());
+ ITelephony telephony = getITelephony();
if (telephony != null) {
return telephony.getSimStateForSlotIndex(slotIndex);
}
@@ -18970,11 +18962,11 @@
@FlaggedApi(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY)
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
- public void setEnableNullCipherNotifications(boolean enable) {
+ public void setNullCipherNotificationsEnabled(boolean enable) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.setEnableNullCipherNotifications(enable);
+ telephony.setNullCipherNotificationsEnabled(enable);
} else {
throw new IllegalStateException("telephony service is null.");
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 09d2108..9b8e62f 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -43,6 +43,7 @@
import android.util.Log;
import com.android.internal.telephony.euicc.IEuiccController;
+import com.android.internal.telephony.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -897,9 +898,7 @@
/** @hide */
public EuiccManager(Context context) {
mContext = context;
- TelephonyManager tm = (TelephonyManager)
- context.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
/** @hide */
@@ -1646,14 +1645,9 @@
private boolean refreshCardIdIfUninitialized() {
// Refresh mCardId if its UNINITIALIZED_CARD_ID
if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- TelephonyManager tm = (TelephonyManager)
- mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
- if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- return false;
- }
- return true;
+ return mCardId != TelephonyManager.UNINITIALIZED_CARD_ID;
}
private static void sendUnavailableError(PendingIntent callbackIntent) {
@@ -1672,6 +1666,23 @@
.get());
}
+ private int getCardIdForDefaultEuicc() {
+ int cardId = TelephonyManager.UNINITIALIZED_CARD_ID;
+
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+ } else {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+
+ return cardId;
+ }
+
/**
* Returns whether the passing portIndex is available.
* A port is available if it is active without enabled profile on it or
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 43eb111..a1fc064 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3247,7 +3247,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.MODIFY_PHONE_STATE)")
- void setEnableNullCipherNotifications(boolean enable);
+ void setNullCipherNotificationsEnabled(boolean enable);
/**
* Get whether notifications are enabled for null cipher or integrity algorithms in use by the
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 566e51a..cbec85e 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -146,6 +146,7 @@
verify(native).setTouchpadPointerSpeed(anyInt())
verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
verify(native).setTouchpadTapToClickEnabled(anyBoolean())
+ verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6015e931..381c574 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -101,7 +101,7 @@
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected TelephonyManager mTelephonyManager;
@Mock protected IPowerManager mPowerManagerService;
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 275a0e2..938a5ed 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -232,3 +232,39 @@
],
},
}
+
+cc_genrule {
+ name: "aapt2_results",
+ srcs: [
+ ":aapt2_tests",
+ "integration-tests/CompileTest/**/*",
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*",
+ "integration-tests/DumpTest/**/*",
+ ],
+ host_supported: true,
+ device_supported: false,
+ target: {
+ windows: {
+ compile_multilib: "64",
+ },
+ },
+ out: ["result.xml"],
+ cmd: "mkdir -p $(genDir)/integration-tests/CompileTest/ && " +
+ "cp $(locations integration-tests/CompileTest/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/CommandTests/ && " +
+ "cp $(locations integration-tests/CommandTests/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/ConvertTest/ && " +
+ "cp $(locations integration-tests/ConvertTest/**/*) $(genDir)/integration-tests/ConvertTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations integration-tests/DumpTest/**/*) $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations :aapt2_tests) $(genDir)/ && " +
+ "$(genDir)/aapt2_tests " +
+ "--gtest_output=xml:$(out) " +
+ ">/dev/null 2>&1 ; true",
+}
+
+phony_rule {
+ name: "aapt2_run_host_unit_tests",
+ phony_deps: ["aapt2_results"],
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 34a1b11..15ae2ba 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -1,22 +1,4 @@
-LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-
-aapt2_results := $(call intermediates-dir-for,PACKAGING,aapt2_run_host_unit_tests)/result.xml
-
-# Target for running host unit tests on post/pre-submit.
-.PHONY: aapt2_run_host_unit_tests
-aapt2_run_host_unit_tests: $(aapt2_results)
-
-$(call dist-for-goals,aapt2_run_host_unit_tests,$(aapt2_results):gtest/aapt2_host_unit_tests_result.xml)
-
-# Always run the tests again, even if they haven't changed
-$(aapt2_results): .KATI_IMPLICIT_OUTPUTS := $(aapt2_results)-nocache
-$(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
- -$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
-
+aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
$(call declare-1p-target,$(aapt2_results))
-
aapt2_results :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
index 292e8da..6480cfc 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
@@ -15,9 +15,9 @@
*/
package com.android.hoststubgen.nativesubstitution;
-import android.util.Log;
-import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+import java.io.PrintStream;
import java.util.Collection;
public class EventLog_host {
@@ -54,7 +54,7 @@
}
}
sb.append(']');
- System.out.println(sb.toString());
+ getRealOut().println(sb.toString());
return sb.length();
}
@@ -66,4 +66,16 @@
Collection<android.util.EventLog.Event> output) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
index ee55c7a..cdfa302 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
@@ -18,6 +18,8 @@
import android.util.Log;
import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+
import java.io.PrintStream;
public class Log_host {
@@ -27,7 +29,6 @@
}
public static int println_native(int bufID, int priority, String tag, String msg) {
- final PrintStream out = System.out;
final String buffer;
switch (bufID) {
case Log.LOG_ID_MAIN: buffer = "main"; break;
@@ -50,7 +51,7 @@
};
for (String s : msg.split("\\n")) {
- out.println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
+ getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
}
return msg.length();
}
@@ -58,4 +59,16 @@
public static int logger_entry_max_payload_native() {
return 4068; // [ravenwood] This is what people use in various places.
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 7c6aa25..60eb47ee 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -63,7 +63,10 @@
*/
public static void onThrowMethodCalled() {
// TODO: Maybe add call tracking?
- throw new RuntimeException("This method is not supported on the host side");
+ throw new RuntimeException(
+ "This method is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
}
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index fc6b862..ba17c75 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -68,7 +68,7 @@
TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
@@ -182,7 +182,7 @@
} catch (java.lang.reflect.InvocationTargetException e) {
var inner = e.getCause();
- assertThat(inner.getMessage()).contains("not supported on the host side");
+ assertThat(inner.getMessage()).contains("not yet supported");
}
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
index 20cc2ec..288c716 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
@@ -60,7 +60,7 @@
TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
index 21700d5..6d57a87 100644
--- a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
+++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -16,11 +16,13 @@
package android.net.wifi.nl80211;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiAnnotations.ChannelWidth;
import android.net.wifi.WifiAnnotations.WifiStandard;
+import android.net.wifi.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -48,6 +50,7 @@
private boolean mChannelWidth320MhzSupported;
private int mMaxNumberTxSpatialStreams;
private int mMaxNumberRxSpatialStreams;
+ private int mMaxNumberAkms;
/** public constructor */
@@ -61,6 +64,7 @@
mChannelWidth320MhzSupported = false;
mMaxNumberTxSpatialStreams = 1;
mMaxNumberRxSpatialStreams = 1;
+ mMaxNumberAkms = 1;
}
/**
@@ -199,6 +203,25 @@
}
/**
+ * Get the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @return maximum number of AKMs
+ */
+ @FlaggedApi(Flags.FLAG_GET_DEVICE_CROSS_AKM_ROAMING_SUPPORT)
+ public int getMaxNumberAkms() {
+ return mMaxNumberAkms;
+ }
+
+ /**
+ * Set the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @hide
+ */
+ public void setMaxNumberAkms(int akms) {
+ mMaxNumberAkms = akms;
+ }
+
+ /**
* Set maximum number of receive spatial streams
*
* @param streams number of streams
@@ -226,7 +249,8 @@
&& mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
&& mChannelWidth320MhzSupported == capa.mChannelWidth320MhzSupported
&& mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
- && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
+ && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams
+ && mMaxNumberAkms == capa.mMaxNumberAkms;
}
/** override hash code */
@@ -235,7 +259,7 @@
return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
m80211beSupported, mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
mChannelWidth320MhzSupported, mMaxNumberTxSpatialStreams,
- mMaxNumberRxSpatialStreams);
+ mMaxNumberRxSpatialStreams, mMaxNumberAkms);
}
/** implement Parcelable interface */
@@ -259,6 +283,7 @@
out.writeBoolean(mChannelWidth320MhzSupported);
out.writeInt(mMaxNumberTxSpatialStreams);
out.writeInt(mMaxNumberRxSpatialStreams);
+ out.writeInt(mMaxNumberAkms);
}
@Override
@@ -276,6 +301,7 @@
.append(mChannelWidth320MhzSupported ? "Yes" : "No");
sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
+ sb.append("mMaxNumberAkms: ").append(mMaxNumberAkms);
return sb.toString();
}
@@ -298,6 +324,7 @@
capabilities.mChannelWidth320MhzSupported = in.readBoolean();
capabilities.mMaxNumberTxSpatialStreams = in.readInt();
capabilities.mMaxNumberRxSpatialStreams = in.readInt();
+ capabilities.mMaxNumberAkms = in.readInt();
return capabilities;
}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
index 7b900fe..5a3ca2b 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
@@ -49,6 +49,7 @@
capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capa.setMaxNumberTxSpatialStreams(2);
capa.setMaxNumberRxSpatialStreams(1);
+ capa.setMaxNumberAkms(2);
Parcel parcel = Parcel.obtain();
capa.writeToParcel(parcel, 0);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb14..02da835 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1130,6 +1130,7 @@
capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capaExpected.setMaxNumberTxSpatialStreams(2);
capaExpected.setMaxNumberRxSpatialStreams(1);
+ capaExpected.setMaxNumberAkms(2);
when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
.thenReturn(capaExpected);
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
new file mode 100644
index 0000000..6ac986e
--- /dev/null
+++ b/wifi/wifi.aconfig
@@ -0,0 +1,9 @@
+package: "android.net.wifi.flags"
+
+flag {
+ name: "get_device_cross_akm_roaming_support"
+ namespace: "wifi"
+ description: "Add new API to get the device support for CROSS-AKM roaming"
+ bug: "313038031"
+ is_fixed_read_only: true
+}