Merge "Make DEBUG not static" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08a09e1..b1f587e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -334,7 +334,7 @@
aconfig_declarations {
name: "android.app.flags-aconfig",
package: "android.app",
- srcs: ["core/java/android/app/activity_manager.aconfig"],
+ srcs: ["core/java/android/app/*.aconfig"],
}
java_aconfig_library {
diff --git a/Android.bp b/Android.bp
index f1a3af2..b1b332a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,6 +389,7 @@
static_libs: [
"android.hardware.common.fmq-V1-java",
"bouncycastle-repackaged-unbundled",
+ "com.android.sysprop.foldlockbehavior",
"framework-internal-utils",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
@@ -525,6 +526,7 @@
required: [
"framework-minus-apex",
"framework-platform-compat-config",
+ "framework-location-compat-config",
"services-platform-compat-config",
"icu4j-platform-compat-config",
"TeleService-platform-compat-config",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 122e627..b215ee4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -68,7 +68,6 @@
"name": "FrameworksInputMethodSystemServerTests",
"options": [
{"include-filter": "com.android.server.inputmethod"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
index b8fef63..6924cb2 100644
--- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -7,7 +7,6 @@
],
"options": [
{"include-filter": "com.android.server.DeviceIdleControllerTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -29,4 +28,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
index b76c582..c0686116 100644
--- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.DeviceIdleControllerTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
@@ -17,4 +16,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index 8504b1f..e649485 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -3,8 +3,6 @@
{
"name": "CtsJobSchedulerTestCases",
"options": [
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.LargeTest"}
]
@@ -13,18 +11,16 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.job"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.job"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
}
],
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
index 73b00b6..e194b8d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.tare"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -12,7 +11,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.tare"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index 9ec799f..a75415e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "CtsUsageStatsTestCases",
"options": [
{"include-filter": "android.app.usage.cts.UsageStatsTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.MediumTest"},
{"exclude-annotation": "androidx.test.filters.LargeTest"}
@@ -21,7 +20,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.usage"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
@@ -37,4 +35,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/api/Android.bp b/api/Android.bp
index 6986ac0..f017a47 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -91,6 +91,7 @@
"framework-media",
"framework-mediaprovider",
"framework-ondevicepersonalization",
+ "framework-pdf",
"framework-permission",
"framework-permission-s",
"framework-scheduling",
diff --git a/boot/Android.bp b/boot/Android.bp
index 93d425e..b33fab6 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -208,6 +208,13 @@
],
}
+genrule { // This module exists to make the srcjar output available to Make.
+ name: "platform-bootclasspath.srcjar",
+ srcs: [":platform-bootclasspath{.srcjar}"],
+ out: ["platform-bootclasspath.srcjar"],
+ cmd: "cp $(in) $(out)",
+}
+
platform_systemserverclasspath {
name: "platform-systemserverclasspath",
}
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 82df555..e7361fe 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -48,6 +48,7 @@
| `vid` | 16-bit integer | Vendor ID |
| `pid` | 16-bit integer | Product ID |
| `bus` | string | Bus that device should use |
+| `port` | string | `phys` value to report |
| `configuration` | object array | uinput device configuration|
| `ff_effects_max` | integer | `ff_effects_max` value |
| `abs_info` | array | Absolute axes information |
diff --git a/core/api/current.txt b/core/api/current.txt
index c9f0639..7fd25b2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5952,6 +5952,7 @@
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
@@ -42903,7 +42904,7 @@
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
- field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -43071,6 +43072,7 @@
field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -54454,8 +54456,8 @@
public class AnimationUtils {
ctor public AnimationUtils();
method public static long currentAnimationTimeMillis();
- method public static long getExpectedPresentationTimeMillis();
- method public static long getExpectedPresentationTimeNanos();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -55027,10 +55029,12 @@
method public int getInitialToolType();
method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
+ method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
method public final void makeCompatible(int);
method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
method public void setInitialSurroundingText(@NonNull CharSequence);
method public void setInitialToolType(int);
+ method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean);
method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d88e00..8748e69 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3223,14 +3223,14 @@
public final class VirtualDeviceParams implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
- method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
method public int getAudioPlaybackSessionId();
method public int getAudioRecordingSessionId();
- method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
- method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
- method public int getDefaultActivityPolicy();
- method public int getDefaultNavigationPolicy();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
+ method @Deprecated public int getDefaultActivityPolicy();
+ method @Deprecated public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
method public int getLockState();
@@ -3238,15 +3238,15 @@
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
- field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+ field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
- field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
- field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+ field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
@@ -3257,12 +3257,12 @@
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
@@ -16731,17 +16731,22 @@
}
public final class SatelliteManager {
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -16768,6 +16773,7 @@
field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 183783b..9a90df9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -263,6 +263,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
@@ -3033,7 +3034,7 @@
"%13s %8s %8s %8s %8s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%21s %8d";
private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
- private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8s %21s %8d";
+ private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8d %21s %8d";
private static final String TWO_COUNT_COLUMN_HEADER = "%21s %8s %21s %8s";
private static final String ONE_ALT_COUNT_COLUMN = "%21s %8s %21s %8d";
@@ -3041,7 +3042,7 @@
private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4;
static void printRow(PrintWriter pw, String format, Object...objs) {
- pw.println(String.format(format, objs));
+ pw.println(String.format(Locale.US, format, objs));
}
@NeverCompile
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4f9225f..ca10d14 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2336,6 +2336,7 @@
OP_WRITE_MEDIA_VIDEO,
OP_READ_MEDIA_IMAGES,
OP_WRITE_MEDIA_IMAGES,
+ OP_READ_MEDIA_VISUAL_USER_SELECTED,
// Nearby devices
OP_BLUETOOTH_SCAN,
OP_BLUETOOTH_CONNECT,
@@ -2376,7 +2377,6 @@
OP_MANAGE_MEDIA,
OP_TURN_SCREEN_ON,
OP_RUN_USER_INITIATED_JOBS,
- OP_READ_MEDIA_VISUAL_USER_SELECTED,
OP_FOREGROUND_SERVICE_SPECIAL_USE,
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 1905b6a..bc6fe61 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,12 +16,15 @@
package android.app;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -31,11 +34,15 @@
*/
@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
public class GrammaticalInflectionManager {
- private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+
+ /** @hide */
+ @NonNull
+ public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
Configuration.GRAMMATICAL_GENDER_NEUTRAL,
Configuration.GRAMMATICAL_GENDER_FEMININE,
- Configuration.GRAMMATICAL_GENDER_MASCULINE));
+ Configuration.GRAMMATICAL_GENDER_MASCULINE)));
private final Context mContext;
private final IGrammaticalInflectionManager mService;
@@ -79,7 +86,7 @@
*/
public void setRequestedApplicationGrammaticalGender(
@Configuration.GrammaticalGender int grammaticalGender) {
- if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
throw new IllegalArgumentException("Unknown grammatical gender");
}
@@ -90,4 +97,44 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the current grammatical gender for all privileged applications. The value will be
+ * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)
+ *
+ * @param grammaticalGender the terms of address the user preferred in system.
+ *
+ * @see Configuration#getGrammaticalGender
+ * @hide
+ */
+ public void setSystemWideGrammaticalGender(
+ @Configuration.GrammaticalGender int grammaticalGender) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ try {
+ mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current grammatical gender of privileged application from the encrypted file,
+ * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}.
+ *
+ * @return the value of grammatical gender
+ *
+ * @see Configuration#getGrammaticalGender
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
+ @Configuration.GrammaticalGender
+ public int getSystemGrammaticalGender() {
+ try {
+ return mService.getSystemGrammaticalGender(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 9366a45..48a4841 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -16,4 +16,14 @@
* Sets a specified app’s app-specific grammatical gender.
*/
void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
- }
\ No newline at end of file
+
+ /**
+ * Sets the grammatical gender to system.
+ */
+ void setSystemWideGrammaticalGender(int userId, int gender);
+
+ /**
+ * Gets the grammatical gender from system.
+ */
+ int getSystemGrammaticalGender(int userId);
+ }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d9..9cf54e3 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@
per-file Broadcast* = file:/BROADCASTS_OWNERS
per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
+# GrammaticalInflectionManager
+per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+
# KeyguardManager
per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 315a055..a29c196 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -19,7 +19,7 @@
"name": "CtsAppOpsTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
new file mode 100644
index 0000000..989ce61
--- /dev/null
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "system_terms_of_address_enabled"
+ namespace: "grammatical_gender"
+ description: "Feature flag for System Terms of Address"
+ bug: "297798866"
+}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index bf00a5a..2f97080 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,6 +23,8 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.Point;
@@ -232,4 +234,10 @@
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
+
+ /**
+ * Creates a new VirtualCamera and registers it with the VirtualCameraProvider.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void registerVirtualCamera(in IVirtualCamera camera);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 7bf2e91..f6a7d2a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -22,6 +22,8 @@
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.virtual.audio.VirtualAudioDevice;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.ComponentName;
import android.content.Context;
@@ -339,6 +341,11 @@
}
@NonNull
+ VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
+ return new VirtualCamera(mVirtualDevice, config);
+ }
+
+ @NonNull
void setShowPointerIcon(boolean showPointerIcon) {
try {
mVirtualDevice.setShowPointerIcon(showPointerIcon);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d338d17..baed7f9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -33,6 +33,8 @@
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.ComponentName;
@@ -851,6 +853,24 @@
}
/**
+ * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
+ *
+ * @param config camera config.
+ * @return newly created camera;
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+ public VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
+ if (!Flags.virtualCamera()) {
+ throw new UnsupportedOperationException(
+ "Flag is not enabled: %s".formatted(Flags.FLAG_VIRTUAL_CAMERA));
+ }
+ return mVirtualDeviceInternal.createVirtualCamera(Objects.requireNonNull(config));
+ }
+
+ /**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0fa78c8..0975cbb 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -94,13 +94,19 @@
/**
* Indicates that activities are allowed by default on this virtual device, unless they are
* explicitly blocked by {@link Builder#setBlockedActivities}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
*/
+ @Deprecated
public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0;
/**
* Indicates that activities are blocked by default on this virtual device, unless they are
* allowed by {@link Builder#setAllowedActivities}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
*/
+ @Deprecated
public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1;
/** @hide */
@@ -113,13 +119,19 @@
/**
* Indicates that tasks are allowed to navigate to other tasks on this virtual device,
* unless they are explicitly blocked by {@link Builder#setBlockedCrossTaskNavigations}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
*/
+ @Deprecated
public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0;
/**
* Indicates that tasks are blocked from navigating to other tasks by default on this virtual
* device, unless allowed by {@link Builder#setAllowedCrossTaskNavigations}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
*/
+ @Deprecated
public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
/** @hide */
@@ -325,7 +337,10 @@
* be be allowed by default.
*
* @see Builder#setAllowedCrossTaskNavigations(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getAllowedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
@@ -340,7 +355,10 @@
* will be be allowed by default.
*
* @see Builder#setBlockedCrossTaskNavigations(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getBlockedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
@@ -355,7 +373,10 @@
*
* @see Builder#setAllowedCrossTaskNavigations
* @see Builder#setBlockedCrossTaskNavigations
+ *
+ * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
*/
+ @Deprecated
@NavigationPolicy
public int getDefaultNavigationPolicy() {
return mDefaultNavigationPolicy;
@@ -366,7 +387,10 @@
* allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getAllowedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
@@ -379,7 +403,10 @@
* that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getBlockedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
@@ -394,7 +421,10 @@
*
* @see Builder#setBlockedActivities
* @see Builder#setAllowedActivities
+ *
+ * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
*/
+ @Deprecated
@ActivityPolicy
public int getDefaultActivityPolicy() {
return mDefaultActivityPolicy;
@@ -743,7 +773,11 @@
*
* @param allowedCrossTaskNavigations A set of tasks {@link ComponentName} allowed to
* navigate to new tasks in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setAllowedCrossTaskNavigations(
@NonNull Set<ComponentName> allowedCrossTaskNavigations) {
@@ -774,7 +808,11 @@
*
* @param blockedCrossTaskNavigations A set of tasks {@link ComponentName} to be
* blocked from navigating to new tasks in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setBlockedCrossTaskNavigations(
@NonNull Set<ComponentName> blockedCrossTaskNavigations) {
@@ -802,7 +840,11 @@
*
* @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
* in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
if (mDefaultActivityPolicyConfigured
@@ -828,7 +870,11 @@
*
* @param blockedActivities A set of {@link ComponentName} to be blocked launching from
* virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
if (mDefaultActivityPolicyConfigured
diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
new file mode 100644
index 0000000..58b850d
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
@@ -0,0 +1,17 @@
+package android.companion.virtual.camera;
+
+import android.companion.virtual.camera.IVirtualCameraSession;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+
+/**
+ * Counterpart of ICameraDevice for virtual camera.
+ *
+ * @hide
+ */
+interface IVirtualCamera {
+
+ IVirtualCameraSession open();
+
+ VirtualCameraHalConfig getHalConfig();
+
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
new file mode 100644
index 0000000..2529807
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+/**
+ * Counterpart of ICameraDeviceSession for virtual camera.
+ *
+ * @hide
+ */
+interface IVirtualCameraSession {
+
+ void configureStream(int width, int height, int format);
+
+ void close();
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
new file mode 100644
index 0000000..791bf0a
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.companion.virtual.IVirtualDevice;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Virtual camera that is used to send image data into system.
+ *
+ * @hide
+ */
+public final class VirtualCamera extends IVirtualCamera.Stub {
+
+ private final VirtualCameraConfig mConfig;
+
+ /**
+ * VirtualCamera device constructor.
+ *
+ * @param virtualDevice The Binder object representing this camera in the server.
+ * @param config Configuration for the new virtual camera
+ */
+ public VirtualCamera(
+ @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
+ mConfig = Objects.requireNonNull(config);
+ Objects.requireNonNull(virtualDevice);
+ try {
+ virtualDevice.registerVirtualCamera(this);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Get the camera session associated with this device */
+ @Override
+ public IVirtualCameraSession open() {
+ // TODO: b/302255544 - Make this async.
+ VirtualCameraSession session = mConfig.getCallback().onOpenSession();
+ return new VirtualCameraSessionInternal(session);
+ }
+
+ /** Returns the configuration of this virtual camera instance. */
+ @NonNull
+ public VirtualCameraConfig getConfig() {
+ return mConfig;
+ }
+
+ /**
+ * Returns the configuration to be used by the virtual camera HAL.
+ *
+ * @hide
+ */
+ @Override
+ @NonNull
+ public VirtualCameraHalConfig getHalConfig() {
+ return mConfig.getHalConfig();
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
new file mode 100644
index 0000000..a7c3d4f
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.hardware.camera2.params.SessionConfiguration;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks
+ * from the framework and the camera system.
+ *
+ * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback)
+ * @hide
+ */
+public interface VirtualCameraCallback {
+
+ /**
+ * Called when a client opens a new camera session for the associated {@link VirtualCamera}
+ *
+ * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)
+ */
+ VirtualCameraSession onOpenSession();
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
new file mode 100644
index 0000000..fb464d5
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.graphics.ImageFormat;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration to create a new {@link VirtualCamera}.
+ *
+ * <p>Instance of this class are created using the {@link VirtualCameraConfig.Builder}.
+ *
+ * @hide
+ */
+public final class VirtualCameraConfig {
+
+ private final String mDisplayName;
+ private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
+ private final VirtualCameraCallback mCallback;
+ private final Executor mCallbackExecutor;
+
+ /**
+ * Builder for {@link VirtualCameraConfig}.
+ *
+ * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
+ * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}.
+ * <li>A name must be set with {@link #setDisplayName(String)}
+ * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)}
+ */
+ public static final class Builder {
+
+ private String mDisplayName;
+ private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>();
+ private Executor mCallbackExecutor;
+ private VirtualCameraCallback mCallback;
+
+ /** Set the visible name of this camera for the user. */
+ // TODO: b/290172356 - Take a resource id instead of displayName
+ @NonNull
+ public Builder setDisplayName(@NonNull String displayName) {
+ mDisplayName = requireNonNull(displayName);
+ return this;
+ }
+
+ /**
+ * Add an available stream configuration fot this {@link VirtualCamera}.
+ *
+ * <p>At least one {@link VirtualCameraStreamConfig} must be added.
+ *
+ * @param width The width of the stream
+ * @param height The height of the stream
+ * @param format The {@link ImageFormat} of the stream
+ */
+ @NonNull
+ public Builder addStreamConfiguration(
+ int width, int height, @ImageFormat.Format int format) {
+ VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig();
+ streamConfig.width = width;
+ streamConfig.height = height;
+ streamConfig.format = format;
+ mStreamConfiguration.add(streamConfig);
+ return this;
+ }
+
+ /**
+ * Sets the {@link VirtualCameraCallback} used by the framework to communicate with the
+ * {@link VirtualCamera} owner.
+ *
+ * <p>Setting a callback is mandatory.
+ *
+ * @param executor The executor onto which the callback methods will be called
+ * @param callback The instance of the callback to be added. Subsequent call to this method
+ * will replace the callback set.
+ */
+ public Builder setCallback(
+ @NonNull Executor executor, @NonNull VirtualCameraCallback callback) {
+ mCallbackExecutor = requireNonNull(executor);
+ mCallback = requireNonNull(callback);
+ return this;
+ }
+
+ /**
+ * Builds a new instance of {@link VirtualCameraConfig}
+ *
+ * @throws NullPointerException if some required parameters are missing.
+ */
+ @NonNull
+ public VirtualCameraConfig build() {
+ return new VirtualCameraConfig(
+ mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback);
+ }
+ }
+
+ private VirtualCameraConfig(
+ @NonNull String displayName,
+ @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
+ @NonNull Executor executor,
+ @NonNull VirtualCameraCallback callback) {
+ mDisplayName = requireNonNull(displayName, "Missing display name");
+ mStreamConfigurations =
+ Collections.unmodifiableSet(
+ requireNonNull(streamConfigurations, "Missing stream configuration"));
+ if (mStreamConfigurations.isEmpty()) {
+ throw new IllegalArgumentException(
+ "At least one StreamConfiguration is needed to create a virtual camera.");
+ }
+ mCallback = requireNonNull(callback, "Missing callback");
+ mCallbackExecutor = requireNonNull(executor, "Missing callback executor");
+ }
+
+ /**
+ * @return The display name of this VirtualCamera
+ */
+ @NonNull
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * Returns an unmodifiable set of the stream configurations added to this {@link
+ * VirtualCameraConfig}.
+ *
+ * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int)
+ */
+ @NonNull
+ public Set<VirtualCameraStreamConfig> getStreamConfigs() {
+ return mStreamConfigurations;
+ }
+
+ /** Returns the callback used to communicate from the server to the client. */
+ @NonNull
+ public VirtualCameraCallback getCallback() {
+ return mCallback;
+ }
+
+ /** Returns the executor onto which the callback should be run. */
+ @NonNull
+ public Executor getCallbackExecutor() {
+ return mCallbackExecutor;
+ }
+
+ /**
+ * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this
+ * {@link VirtualCameraConfig}
+ */
+ @NonNull
+ public VirtualCameraHalConfig getHalConfig() {
+ VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig();
+ halConfig.displayName = mDisplayName;
+ halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]);
+ return halConfig;
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
new file mode 100644
index 0000000..7070a38
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
@@ -0,0 +1,12 @@
+package android.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+
+/**
+ * Configuration for VirtualCamera to be passed to the server and HAL service.
+ * @hide
+ */
+parcelable VirtualCameraHalConfig {
+ String displayName;
+ VirtualCameraStreamConfig[] streamConfigs;
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
new file mode 100644
index 0000000..c25d977
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+/***
+ * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing
+ * images from a {@link VirtualCamera}.
+ * @hide
+ */
+// TODO: b/289881985 - This is just a POC implementation for now, this will be extended
+// to a full featured Camera Session
+public interface VirtualCameraSession {
+
+ /** Close the session and release its resources. */
+ default void close() {}
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
new file mode 100644
index 0000000..da168de
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.graphics.ImageFormat;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}.
+ *
+ * @hide
+ */
+final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub {
+
+ @SuppressWarnings("FieldCanBeLocal")
+ // TODO: b/289881985: Will be used once connected with the CameraService
+ private final VirtualCameraSession mVirtualCameraSession;
+
+ VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) {
+ mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession);
+ }
+
+ @Override
+ public void configureStream(int width, int height, @ImageFormat.Format int format) {}
+
+ public void close() {}
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
new file mode 100644
index 0000000..304d455
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+/**
+ * A stream configuration supported by a virtual camera
+ * @hide
+ */
+parcelable VirtualCameraStreamConfig {
+ int width;
+ int height;
+ int format;
+}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ab669cc..b0ab11f 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -45,7 +45,8 @@
]
},
{
- "name":"CarrierAppIntegrationTestCases"
+ "name":"CarrierAppIntegrationTestCases",
+ "keywords": ["internal"]
},
{
"name":"CtsSilentUpdateHostTestCases"
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0c8eb02..b5efe58 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -13,3 +13,17 @@
description: "Feature flag to enable the archiving feature."
bug: "278553670"
}
+
+flag {
+ name: "prevent_sdk_lib_app"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the prevent sdk-library be an application."
+ bug: "295843617"
+}
+
+flag {
+ name: "stay_stopped"
+ namespace: "backstage_power"
+ description: "Feature flag to improve stopped state enforcement"
+ bug: "296644915"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 07ff7be..ea0f049 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -6,3 +6,10 @@
description: "Save guest and device policy global restrictions on the SYSTEM user's XML file."
bug: "301067944"
}
+
+flag {
+ name: "bind_wallpaper_service_on_its_own_thread_during_a_user_switch"
+ namespace: "multiuser"
+ description: "Bind wallpaper service on its own thread instead of system_server's main handler during a user switch."
+ bug: "302100344"
+}
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
index 71ad687..5b5ea37 100644
--- a/core/java/android/nfc/TEST_MAPPING
+++ b/core/java/android/nfc/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "NfcManagerTests"
+ },
+ {
+ "name": "CtsNfcTestCases"
}
]
}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index ad3abd9..2d6e09a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -8,7 +8,6 @@
"name": "FrameworksVibratorCoreTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -21,7 +20,6 @@
"name": "FrameworksVibratorServicesTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -34,7 +32,6 @@
"name": "CtsVibratorTestCases",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 80b7d40..4c8ef97 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2940,6 +2940,12 @@
* Used to check if the context user is a restricted profile. Restricted profiles
* may have a reduced number of available apps, app restrictions, and account restrictions.
*
+ * <p>The caller must be in the same profile group as the context user or else hold
+ * <li>{@link android.Manifest.permission#MANAGE_USERS},
+ * <li>or {@link android.Manifest.permission#CREATE_USERS},
+ * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link android.Manifest.permission#QUERY_USERS}.
+ *
* @return whether the context user is a restricted profile.
* @hide
*/
@@ -2963,6 +2969,12 @@
* Check if a user is a restricted profile. Restricted profiles may have a reduced number of
* available apps, app restrictions, and account restrictions.
*
+ * <p>Requires
+ * <li>{@link android.Manifest.permission#MANAGE_USERS},
+ * <li>or {@link android.Manifest.permission#CREATE_USERS},
+ * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link android.Manifest.permission#QUERY_USERS}.
+ *
* @param user the user to check
* @return whether the user is a restricted profile.
* @hide
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index 7b8d52f..468c451 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsNotificationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -21,9 +18,6 @@
"name": "FrameworksUiServicesTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
index 0fe974a..c9bd2ca 100644
--- a/core/java/android/text/TEST_MAPPING
+++ b/core/java/android/text/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsTextTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
new file mode 100644
index 0000000..b0aa72a
--- /dev/null
+++ b/core/java/android/text/flags/fix_double_underline.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "fix_double_underline"
+ namespace: "text"
+ description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+ bug: "297336724"
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5eaded2..7674131 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1328,6 +1328,14 @@
public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto";
/**
+ * Hint indicating that the developer intends to fill this view with output from
+ * CredentialManager.
+ *
+ * @hide
+ */
+ public static final String AUTOFILL_HINT_CREDENTIAL_MANAGER = "credential";
+
+ /**
* Hints for the autofill services that describes the content of the view.
*/
private @Nullable String[] mAutofillHints;
@@ -32627,6 +32635,7 @@
* @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
* @param enabled whether auto handwriting initiation is enabled for this view.
* @attr ref android.R.styleable#View_autoHandwritingEnabled
+ * @see EditorInfo#setStylusHandwritingEnabled(boolean)
*/
public void setAutoHandwritingEnabled(boolean enabled) {
if (enabled) {
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 8ba8b8c..a07b62f 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,7 +16,11 @@
package android.view.animation;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
+import static android.view.flags.Flags.expectedPresentationTimeApi;
+
import android.annotation.AnimRes;
+import android.annotation.FlaggedApi;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
@@ -151,7 +155,12 @@
* @return the expected presentation time of a frame in the
* {@link System#nanoTime()} time base.
*/
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
public static long getExpectedPresentationTimeNanos() {
+ if (!expectedPresentationTimeApi()) {
+ return SystemClock.uptimeMillis();
+ }
+
AnimationState state = sAnimationState.get();
return state.mExpectedPresentationTimeNanos;
}
@@ -164,6 +173,7 @@
* @return the expected presentation time of a frame in the
* {@link SystemClock#uptimeMillis()} time base.
*/
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
public static long getExpectedPresentationTimeMillis() {
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 4cb8788..6cf185a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1464,7 +1464,7 @@
if (infos.size() == 0) {
throw new IllegalArgumentException("No VirtualViewInfo found");
}
- if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) {
+ if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ view.getAutofillId().toString());
@@ -1488,7 +1488,7 @@
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
- if (v.isCredential()
+ if (isCredmanRequested(v)
&& mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
@@ -3434,6 +3434,22 @@
}
}
+ private boolean isCredmanRequested(View view) {
+ if (view.isCredential()) {
+ return true;
+ }
+ String[] hints = view.getAutofillHints();
+ if (hints == null) {
+ return false;
+ }
+ for (String hint : hints) {
+ if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Find a single view by its id.
*
diff --git a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
index e3972ad..13a6f8d 100644
--- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
@@ -12,4 +12,11 @@
namespace: "toolkit"
description: "Feature flag for toolkit to set frame rate"
bug: "293512962"
+}
+
+flag {
+ name: "expected_presentation_time_api"
+ namespace: "toolkit"
+ description: "Feature flag for using expected presentation time of the Choreographer"
+ bug: "278730197"
}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 4e5cec7..a92420a 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -23,7 +23,9 @@
import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME;
import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
+import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -45,6 +47,7 @@
import android.view.MotionEvent.ToolType;
import android.view.View;
import android.view.autofill.AutofillId;
+import android.widget.Editor;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -716,6 +719,33 @@
return set;
}
+ private boolean mIsStylusHandwritingEnabled;
+
+ /**
+ * Set {@code true} if the {@link Editor} has
+ * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
+ * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+ * it supports stylus handwriting.
+ *
+ * @param enabled {@code true} if stylus handwriting is enabled.
+ * @see View#setAutoHandwritingEnabled(boolean)
+ */
+ @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED)
+ public void setStylusHandwritingEnabled(boolean enabled) {
+ mIsStylusHandwritingEnabled = enabled;
+ }
+
+ /**
+ * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+ * {@code false} by default.
+ * @see #setStylusHandwritingEnabled(boolean)
+ * @see InputMethodManager#isStylusHandwritingAvailable()
+ */
+ @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED)
+ public boolean isStylusHandwritingEnabled() {
+ return mIsStylusHandwritingEnabled;
+ }
+
/**
* If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
* matter what user ID the calling process has.
@@ -1211,6 +1241,7 @@
pw.println(prefix + "supportedHandwritingGesturePreviewTypes="
+ InputMethodDebug.handwritingGestureTypeFlagsToString(
mSupportedHandwritingGesturePreviewTypes));
+ pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled);
pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
if (targetInputMethodUser != null) {
pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1277,6 +1308,9 @@
dest.writeBundle(extras);
dest.writeInt(mSupportedHandwritingGestureTypes);
dest.writeInt(mSupportedHandwritingGesturePreviewTypes);
+ if (Flags.editorinfoHandwritingEnabled()) {
+ dest.writeBoolean(mIsStylusHandwritingEnabled);
+ }
dest.writeBoolean(mInitialSurroundingText != null);
if (mInitialSurroundingText != null) {
mInitialSurroundingText.writeToParcel(dest, flags);
@@ -1316,6 +1350,9 @@
res.extras = source.readBundle();
res.mSupportedHandwritingGestureTypes = source.readInt();
res.mSupportedHandwritingGesturePreviewTypes = source.readInt();
+ if (Flags.editorinfoHandwritingEnabled()) {
+ res.mIsStylusHandwritingEnabled = source.readBoolean();
+ }
boolean hasInitialSurroundingText = source.readBoolean();
if (hasInitialSurroundingText) {
res.mInitialSurroundingText =
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c144289..c14b510 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -6,4 +6,12 @@
description: "Feature flag for refactoring InsetsController and removing ImeInsetsSourceConsumer"
bug: "298172246"
is_fixed_read_only: true
+}
+
+flag {
+ name: "editorinfo_handwriting_enabled"
+ namespace: "input_method"
+ description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled"
+ bug: "293898187"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index d25c8a8..7b7e341 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -25,9 +25,12 @@
interface ITaskFragmentOrganizerController {
/**
- * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system
+ * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional
+ * system capabilities.
*/
- void registerOrganizer(in ITaskFragmentOrganizer organizer);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)")
+ void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer);
/**
* Unregisters a previously registered TaskFragmentOrganizer.
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index f785a3d..a6c9cec 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -22,9 +22,11 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.os.Bundle;
import android.os.IBinder;
@@ -32,6 +34,8 @@
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -140,12 +144,34 @@
}
/**
- * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
*/
@CallSuper
public void registerOrganizer() {
+ // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed.
try {
- getController().registerOrganizer(mInterface);
+ getController().registerOrganizer(mInterface, false /* isSystemOrganizer */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
+ *
+ * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer
+ * will have additional system capabilities, including: (1) it will receive SurfaceControl for
+ * the organized TaskFragment, and (2) it needs to update the
+ * {@link android.view.SurfaceControl} following the window change accordingly.
+ *
+ * @hide
+ */
+ @CallSuper
+ @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true)
+ @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+ public void registerOrganizer(boolean isSystemOrganizer) {
+ try {
+ getController().registerOrganizer(mInterface, isSystemOrganizer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 3c5d60d..4dada10 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -30,6 +30,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -192,6 +193,9 @@
@Nullable
private TaskFragmentParentInfo mTaskFragmentParentInfo;
+ @Nullable
+ private SurfaceControl mSurfaceControl;
+
public Change(@ChangeType int type) {
mType = type;
}
@@ -206,6 +210,7 @@
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mActivityToken = in.readStrongBinder();
mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
+ mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR);
}
@Override
@@ -219,6 +224,7 @@
dest.writeTypedObject(mActivityIntent, flags);
dest.writeStrongBinder(mActivityToken);
dest.writeTypedObject(mTaskFragmentParentInfo, flags);
+ dest.writeTypedObject(mSurfaceControl, flags);
}
/** The change is related to the TaskFragment created with this unique token. */
@@ -306,6 +312,13 @@
return this;
}
+ /** @hide */
+ @NonNull
+ public Change setTaskFragmentSurfaceControl(@Nullable SurfaceControl sc) {
+ mSurfaceControl = sc;
+ return this;
+ }
+
@ChangeType
public int getType() {
return mType;
@@ -359,6 +372,21 @@
return mTaskFragmentParentInfo;
}
+ /**
+ * Gets the {@link SurfaceControl} of the TaskFragment. This field is {@code null} for
+ * a regular {@link TaskFragmentOrganizer} and is only available for a system
+ * {@link TaskFragmentOrganizer} in the
+ * {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_APPEARED} event. See
+ * {@link ITaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer,
+ * boolean)}
+ *
+ * @hide
+ */
+ @Nullable
+ public SurfaceControl getTaskFragmentSurfaceControl() {
+ return mSurfaceControl;
+ }
+
@Override
public String toString() {
return "Change{ type=" + mType + " }";
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 663067c..0b69030 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -48,7 +48,6 @@
static final String TAG = "PackageMonitor";
final IntentFilter mPackageFilt;
- final IntentFilter mNonDataFilt;
Context mRegisteredContext;
Handler mRegisteredHandler;
@@ -78,13 +77,6 @@
if (isCore) {
mPackageFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
-
- mNonDataFilt = new IntentFilter();
- // UserController sends the broadcast
- mNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
- if (isCore) {
- mNonDataFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- }
}
@UnsupportedAppUsage
@@ -111,10 +103,8 @@
mRegisteredHandler = Objects.requireNonNull(handler);
if (user != null) {
context.registerReceiverAsUser(this, user, mPackageFilt, null, mRegisteredHandler);
- context.registerReceiverAsUser(this, user, mNonDataFilt, null, mRegisteredHandler);
} else {
context.registerReceiver(this, mPackageFilt, null, mRegisteredHandler);
- context.registerReceiver(this, mNonDataFilt, null, mRegisteredHandler);
}
if (mPackageMonitorCallback == null) {
PackageManager pm = mRegisteredContext.getPackageManager();
@@ -216,9 +206,6 @@
return false;
}
- public void onHandleUserStop(Intent intent, int userHandle) {
- }
-
public void onUidRemoved(int uid) {
}
@@ -463,10 +450,6 @@
intent.getIntExtra(Intent.EXTRA_UID, 0), true);
} else if (Intent.ACTION_UID_REMOVED.equals(action)) {
onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
- } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
- onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- }
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mAppearingPackages = pkgList;
diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
new file mode 100644
index 0000000..4e3888a
--- /dev/null
+++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.foldables;
+
+import android.content.res.Resources;
+import android.sysprop.FoldLockBehaviorProperties;
+
+import com.android.internal.R;
+
+/**
+ * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing
+ */
+public class FoldLockSettingAvailabilityProvider {
+
+ boolean mFoldLockBehaviorResourceValue;
+
+ public FoldLockSettingAvailabilityProvider(Resources resources) {
+ mFoldLockBehaviorResourceValue = resources.getBoolean(
+ R.bool.config_fold_lock_behavior);
+ }
+
+ public boolean isFoldLockBehaviorAvailable() {
+ return mFoldLockBehaviorResourceValue
+ && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false);
+ }
+}
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index ddfd0ed..c09181f 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsRoleTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
@@ -15,7 +15,7 @@
"include-filter": "android.permission.cts.PermissionControllerTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7eeac29..4d8eeac 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -316,7 +316,14 @@
*/
void requestTileServiceListeningState(in ComponentName componentName);
- void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
+ void requestAddTile(
+ int callingUid,
+ in ComponentName componentName,
+ in CharSequence appName,
+ in CharSequence label,
+ in Icon icon,
+ in IAddTileResultCallback callback
+ );
void cancelRequestAddTile(in String packageName);
/** Notifies System UI about an update to the media tap-to-transfer sender state. */
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 878e6b3..71d696e 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -172,28 +172,17 @@
<integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer>
<java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" />
- <!-- Telephony config for services supported by satellite providers. The format of each config
- string in the array is as follows: "PLMN_1:service_1,service_2,..."
- where PLMN is the satellite PLMN of a provider and service is an integer with the
- following value:
- 1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
- 2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
- 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
- 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
- 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
- 6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}
- Example of a config string: "10011:2,3"
+ <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
+ to identify providers that should be ignored if the carrier config
+ carrier_supported_satellite_services_per_provider_bundle does not support them.
+ -->
+ <string-array name="config_satellite_providers" translatable="false"></string-array>
+ <java-symbol type="array" name="config_satellite_providers" />
- The PLMNs not configured in this array will be ignored and will not be used for satellite
- scanning. -->
- <string-array name="config_satellite_services_supported_by_providers" translatable="false">
- </string-array>
- <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
-
- <!-- The identifier of the satellite's eSIM profile preloaded on the device. The identifier is
- composed of MCC and MNC of the satellite PLMN with the format "mccmnc". -->
- <string name="config_satellite_esim_identifier" translatable="false"></string>
- <java-symbol type="string" name="config_satellite_esim_identifier" />
+ <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
+ of the satellite PLMN with the format "mccmnc". -->
+ <string name="config_satellite_sim_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_identifier" />
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
diff --git a/core/sysprop/Android.bp b/core/sysprop/Android.bp
index f89099e..512a2eb 100644
--- a/core/sysprop/Android.bp
+++ b/core/sysprop/Android.bp
@@ -36,3 +36,10 @@
api_packages: ["android.sysprop"],
vendor_available: false,
}
+
+sysprop_library {
+ name: "com.android.sysprop.foldlockbehavior",
+ srcs: ["FoldLockBehaviorProperties.sysprop"],
+ property_owner: "Platform",
+ api_packages: ["android.sysprop"],
+}
diff --git a/core/sysprop/FoldLockBehaviorProperties.sysprop b/core/sysprop/FoldLockBehaviorProperties.sysprop
new file mode 100644
index 0000000..d337954
--- /dev/null
+++ b/core/sysprop/FoldLockBehaviorProperties.sysprop
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: "android.sysprop.FoldLockBehaviorProperties"
+owner: Platform
+
+prop {
+ api_name: "fold_lock_setting_enabled"
+ type: Boolean
+ prop_name: "persist.fold_lock_setting_enabled"
+ scope: Internal
+ access: Readonly
+}
diff --git a/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt b/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt
diff --git a/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt b/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt
new file mode 100644
index 0000000..1327470
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt
@@ -0,0 +1,9 @@
+props {
+ module: "android.sysprop.FoldLockBehaviorProperties"
+ prop {
+ api_name: "fold_lock_setting_enabled"
+ type: Boolean
+ prop_name: "persist.fold_lock_setting_enabled"
+ scope: Internal
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 5d92296..49ed3a8 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,13 +18,20 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.IUriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -34,6 +41,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -457,6 +465,81 @@
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
+ @SmallTest
+ public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ false);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+ assertThat(loadedDrawable).isNotNull();
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight());
+ loadedDrawable.draw(new Canvas(test1));
+
+ bit1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-original.png")));
+ test1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-test.png")));
+ if (!equalBitmaps(bit1, test1)) {
+ findBitmapDifferences(bit1, test1);
+ fail("bitmap1 differs, check " + dir);
+ }
+ }
+
+ @SmallTest
+ public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ true);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ assertThat(loadedDrawable).isNull();
+ }
+
+
// ======== utils ========
static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
@@ -541,4 +624,77 @@
}
L(sb.toString());
}
-}
+
+ private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {
+
+ final ArrayList<CheckRequest> mRequests = new ArrayList<>();
+ final boolean mRejectCheckRequests;
+
+ TestableIUriGrantsManager(boolean rejectCheckRequests) {
+ this.mRejectCheckRequests = rejectCheckRequests;
+ }
+
+ @Override
+ public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
+ int i2, int i3) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void clearGrantedUriPermissions(String s, int i) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int checkGrantUriPermission_ignoreNonSystem(
+ int uid, String packageName, Uri uri, int mode, int userId)
+ throws RemoteException {
+ CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
+ mRequests.add(r);
+ if (mRejectCheckRequests) {
+ throw new SecurityException();
+ } else {
+ return uid;
+ }
+ }
+
+ static class CheckRequest {
+ final int mCallingUid;
+ final String mPackageName;
+ final Uri mUri;
+ final int mMode;
+ final int mUserId;
+
+ CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
+ this.mCallingUid = callingUid;
+ this.mPackageName = packageName;
+ this.mUri = uri;
+ this.mMode = mode;
+ this.mUserId = userId;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 6e73b9f..4839dd2 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -503,6 +503,7 @@
+ "prefix: hintLocales=null\n"
+ "prefix: supportedHandwritingGestureTypes=(none)\n"
+ "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+ + "prefix: isStylusHandwritingEnabled=false\n"
+ "prefix: contentMimeTypes=null\n");
}
@@ -521,6 +522,9 @@
info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
info.setSupportedHandwritingGesturePreviews(
Stream.of(SelectGesture.class).collect(Collectors.toSet()));
+ if (Flags.editorinfoHandwritingEnabled()) {
+ info.setStylusHandwritingEnabled(true);
+ }
info.packageName = "android.view.inputmethod";
info.autofillId = new AutofillId(123);
info.fieldId = 456;
@@ -544,6 +548,8 @@
+ "prefix2: hintLocales=[en,es,zh]\n"
+ "prefix2: supportedHandwritingGestureTypes=SELECT\n"
+ "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
+ + "prefix2: isStylusHandwritingEnabled="
+ + Flags.editorinfoHandwritingEnabled() + "\n"
+ "prefix2: contentMimeTypes=[image/png]\n"
+ "prefix2: targetInputMethodUserId=10\n");
}
@@ -565,6 +571,7 @@
+ "prefix: hintLocales=null\n"
+ "prefix: supportedHandwritingGestureTypes=(none)\n"
+ "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+ + "prefix: isStylusHandwritingEnabled=false\n"
+ "prefix: contentMimeTypes=null\n");
}
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a8b4032..a5bbeb5 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -17,6 +17,7 @@
package android.window.flags;
import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
import android.platform.test.annotations.Presubmit;
@@ -42,4 +43,10 @@
// No crash when accessing the flag.
syncWindowConfigUpdateFlag();
}
+
+ @Test
+ public void testTaskFragmentSystemOrganizerFlag() {
+ // No crash when accessing the flag.
+ taskFragmentSystemOrganizerFlag();
+ }
}
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index c7eddabe..a339907 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -17,6 +17,7 @@
package com.android.internal.content;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -67,7 +68,7 @@
spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
- verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+ verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
eq(null), eq(mMockHandler));
assertThrows(IllegalStateException.class,
@@ -138,19 +139,6 @@
}
@Test
- public void testPackageMonitorDoHandlePackageEventUserStop() throws Exception {
- PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
-
- Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
- spyPackageMonitor.doHandlePackageEvent(intent);
-
- verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
- verify(spyPackageMonitor, times(1)).onHandleUserStop(eq(intent), eq(FAKE_USER_ID));
- verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
- }
-
- @Test
public void testPackageMonitorDoHandlePackageEventExternalApplicationAvailable()
throws Exception {
PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
index 2f3afa6..54a5ff1 100644
--- a/core/tests/vibrator/TEST_MAPPING
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksVibratorCoreTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 708feeb..5509f00 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -24,9 +24,12 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.IUriGrantsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresPermission;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -530,6 +536,46 @@
return loadDrawable(context);
}
+ /**
+ * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
+ * to load the resource. The check will be performed using the permissions of the passed uid,
+ * and not those of the caller.
+ * <p>
+ * This should be called for {@link Icon} objects that come from a not trusted source and may
+ * contain a URI.
+ *
+ * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
+ * return {@code null}.
+ *
+ * @see #loadDrawable
+ *
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public Drawable loadDrawableCheckingUriGrant(
+ Context context,
+ IUriGrantsManager iugm,
+ int callingUid,
+ String packageName
+ ) {
+ if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
+ try {
+ iugm.checkGrantUriPermission_ignoreNonSystem(
+ callingUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(getUri()),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(getUri())
+ );
+ } catch (SecurityException | RemoteException e) {
+ Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
+ return null;
+ }
+ }
+ return loadDrawable(context);
+ }
+
/** @hide */
public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7faf380..705e387 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -435,10 +435,10 @@
<dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in desktop mode, but not freeform. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">104dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in freeform desktop windowing mode. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">104dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">52dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
index cc37bd3a4..d45e126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -112,7 +112,7 @@
viewPositionOnTouchDown.set(v.translationX, v.translationY)
performedLongClick = false
- v.handler.postDelayed({
+ v.handler?.postDelayed({
if (v.isLongClickable) {
performedLongClick = v.performLongClick()
}
@@ -122,7 +122,7 @@
MotionEvent.ACTION_MOVE -> {
if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
movedEnough = true
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
if (movedEnough) {
@@ -138,7 +138,7 @@
} else if (!performedLongClick) {
v.performClick()
} else {
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
velocityTracker.clear()
@@ -146,7 +146,7 @@
}
MotionEvent.ACTION_CANCEL -> {
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
velocityTracker.clear()
movedEnough = false
}
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 3d825f0..4ea14f4 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
@@ -720,6 +720,8 @@
mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -767,6 +769,8 @@
mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
options1 = options1 != null ? options1 : new Bundle();
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 de03f58..e0635ac 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
@@ -322,6 +322,7 @@
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
mAnimations.remove(transition);
+ info.releaseAllSurfaces();
finishCallback.onTransitionFinished(null /* wct */);
};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index f82b212..a7a11de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -163,6 +163,9 @@
final ImageButton splitscreenBtn = windowingPillView.findViewById(
R.id.split_screen_button);
final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+ // TODO: Remove once implemented.
+ floatingBtn.setVisibility(View.GONE);
+
final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
fullscreenBtn.setOnClickListener(mOnClickListener);
splitscreenBtn.setOnClickListener(mOnClickListener);
@@ -196,6 +199,9 @@
}
final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
selectBtn.setOnClickListener(mOnClickListener);
+ final Button screenshotBtn = moreActionsPillView.findViewById(R.id.screenshot_button);
+ // TODO: Remove once implemented.
+ screenshotBtn.setVisibility(View.GONE);
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
new file mode 100644
index 0000000..2cd08a4a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.NetflixAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Netflix app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch Netflix and start playing a video
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+ ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
+ NetflixAppHelper.WATCH_ACTIVITY)
+ )
+ standardAppHelper.waitForVideoPlaying()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ standardAppHelper.exit(wmHelper)
+ }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions {
+ tapl.goHomeFromImmersiveFullscreenApp()
+ }
+ }
+
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // Netflix uses source rect hint, so PiP overlay is never present
+ }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0),
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index b0e847f..e37d806 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -19,15 +19,10 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
import org.junit.Test
open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index f847e40..2a50912 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -19,15 +19,10 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
import org.junit.Test
open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 31e52e2..d5da1a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 0870073..7fdcb9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 1bb6c45..308e954 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index bb084d6..39e75bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index bc5857f..e18da17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -19,15 +19,10 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
import org.junit.Test
open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index e4faa4a..00d60e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -19,15 +19,10 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
import org.junit.Test
open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 7431974..d7efbc8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index c2b0609..4eece3f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 7d1072d..d96b056 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 79760d1..809b690 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index ff3729a..bbdf2d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index ffbcfe4..5c29fd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 5e937c0..a7398eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index 89db8a0..eae88ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index bf4ee7a..7e8ee04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 7e96f06..9295c33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index a84deac..4b59e9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
import org.junit.Test
open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index afcc743..5ff36d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
import org.junit.Test
open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 2f96f5f..c0cb721 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index efbeb76..8c14088 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 232fccc..7b6614b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index d7f2845..5df5be9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 019c946..9d63003 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 5678861..9fa04b2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index b3c4ea2..9386aa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
import org.junit.Test
open class SwitchBetweenSplitPairsGesturalNavLandscape :
SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index f88180f..5ef2167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
import org.junit.Test
open class SwitchBetweenSplitPairsGesturalNavPortrait :
SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 391cb9b7..9caab9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -18,18 +18,13 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index 4af133d..bf484e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -18,18 +18,13 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2d93047..02c9d30 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -35,7 +35,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 270dbc4..83d9f65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -39,7 +39,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
similarity index 91%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index 0dc16f4..cb29a21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat;
@@ -27,10 +27,7 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.bubbles.BubbleOverflow;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.TestableBubblePositioner;
+import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
index 9655f97..f8eb50b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -38,7 +38,7 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f2c29f4..dea1617 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -43,7 +43,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRemoteTransition
+import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 69f664a..499e339 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -37,8 +37,8 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index d542139..ebc284b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -62,9 +62,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRemoteTransition;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -75,6 +73,8 @@
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.TestRemoteTransition;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index a57a7bf..d598678 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -94,7 +94,6 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
index 0df42b3..39ab238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
import android.os.IBinder;
import android.os.RemoteException;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
similarity index 98%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index a658375..8343858 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e1dd145..ff1eedb 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -143,6 +143,7 @@
"libcrypto",
"libsync",
"libui",
+ "aconfig_text_flags_c_lib",
],
static_libs: [
"libEGL_blobCache",
@@ -712,11 +713,13 @@
],
static_libs: [
+ "libflagtest",
"libgmock",
"libhwui_static",
],
shared_libs: [
"libmemunreachable",
+ "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
@@ -756,6 +759,7 @@
"tests/unit/TestUtilsTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
+ "tests/unit/UnderlineTest.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 0000000..ffb329d
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+ return com_android_text_flags_fix_double_underline();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+} // namespace text_feature
+
+} // namespace android
+
+#endif // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8394c3c..31fc929 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
#include <utility>
#include "CanvasProperty.h"
+#include "FeatureFlags.h"
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
@@ -795,7 +796,9 @@
sk_sp<SkTextBlob> textBlob(builder.make());
applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
- drawTextDecorations(x, y, totalAdvance, paintCopy);
+ if (!text_feature::fix_double_underline()) {
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
+ }
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2351797..80b6c03 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
#include "Canvas.h"
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "RenderNode.h"
#include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
#include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
namespace android {
@@ -34,13 +35,6 @@
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
- const Paint& paint, Canvas* canvas) {
- const SkScalar strokeWidth = fmax(thickness, 1.0f);
- const SkScalar bottom = top + strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
-}
-
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
// paint has already been filtered by our caller, so we can ignore any filter
const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@
}
}
-static void simplifyPaint(int color, Paint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
- : layout(layout)
- , canvas(canvas)
- , paint(paint)
- , x(x)
- , y(y)
- , totalAdvance(totalAdvance) {}
-
- void operator()(size_t start, size_t end) {
- auto glyphFunc = [&](uint16_t* text, float* positions) {
- for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
- text[textIndex++] = layout.getGlyphId(i);
- positions[posIndex++] = x + layout.getX(i);
- positions[posIndex++] = y + layout.getY(i);
- }
- };
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
- Paint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
- // inner
- gDrawTextBlobMode = DrawTextBlobMode::HctInner;
- Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
- gDrawTextBlobMode = DrawTextBlobMode::Normal;
- } else {
- // standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
- }
- }
-
-private:
- const minikin::Layout& layout;
- Canvas* canvas;
- const Paint& paint;
- float x;
- float y;
- float totalAdvance;
-};
-
void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
int glyphCount, const Paint& paint) {
// Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -182,6 +109,31 @@
DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
+
+ if (text_feature::fix_double_underline()) {
+ Paint copied(paint);
+ PaintFilter* filter = getPaintFilter();
+ if (filter != nullptr) {
+ filter->filterFullPaint(&copied);
+ }
+ const bool isUnderline = copied.isUnderline();
+ const bool isStrikeThru = copied.isStrikeThru();
+ if (isUnderline || isStrikeThru) {
+ const SkScalar left = x;
+ const SkScalar right = x + layout.getAdvance();
+ if (isUnderline) {
+ const SkScalar top = y + f.getUnderlinePosition();
+ drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+ }
+ if (isStrikeThru) {
+ float textSize = paint.getSkFont().getSize();
+ const float position = textSize * Paint::kStdStrikeThru_Top;
+ const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+ const SkScalar top = y + position;
+ drawStroke(left, right, top, thickness, copied, this);
+ }
+ }
+ }
}
void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d..9ec023b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y,float totalAdvance) = 0;
+ float y, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 0000000..2e6e976
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+ const Paint& paint, Canvas* canvas) {
+ const SkScalar strokeWidth = fmax(thickness, 1.0f);
+ const SkScalar bottom = top + strokeWidth;
+ canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+ float y, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , totalAdvance(totalAdvance)
+ , underlinePosition(0)
+ , underlineThickness(0) {}
+
+ void operator()(size_t start, size_t end) {
+ auto glyphFunc = [&](uint16_t* text, float* positions) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ };
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+ Paint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+ // inner
+ gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+ Paint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+ gDrawTextBlobMode = DrawTextBlobMode::Normal;
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+ }
+
+ if (text_feature::fix_double_underline()) {
+ // Extract underline position and thickness.
+ if (paint.isUnderline()) {
+ SkFontMetrics metrics;
+ paint.getSkFont().getMetrics(&metrics);
+ const float textSize = paint.getSkFont().getSize();
+ SkScalar position;
+ if (!metrics.hasUnderlinePosition(&position)) {
+ position = textSize * Paint::kStdUnderline_Top;
+ }
+ SkScalar thickness;
+ if (!metrics.hasUnderlineThickness(&thickness)) {
+ thickness = textSize * Paint::kStdUnderline_Thickness;
+ }
+
+ // If multiple fonts are used, use the most bottom position and most thick stroke
+ // width as the underline position. This follows the CSS standard:
+ // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+ // <quote>
+ // The exact position and thickness of line decorations is UA-defined in this level.
+ // However, for underlines and overlines the UA must use a single thickness and
+ // position on each line for the decorations deriving from a single decorating box.
+ // </quote>
+ underlinePosition = std::max(underlinePosition, position);
+ underlineThickness = std::max(underlineThickness, thickness);
+ }
+ }
+ }
+
+ float getUnderlinePosition() const { return underlinePosition; }
+ float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+ const minikin::Layout& layout;
+ Canvas* canvas;
+ const Paint& paint;
+ float x;
+ float y;
+ float totalAdvance;
+ float underlinePosition;
+ float underlineThickness;
+};
+
+} // namespace android
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7..0fffee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@
p.writeFloat(info.fDisplayRatioHdr);
// base image type
p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
- // type
- p.writeInt32(static_cast<int32_t>(info.fType));
#else
doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
@@ -232,7 +230,6 @@
info.fDisplayRatioSdr = p.readFloat();
info.fDisplayRatioHdr = p.readFloat();
info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
- info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
fromJava(nativeObject)->info = info;
#else
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 0000000..db2be20
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+ std::vector<minikin::FontVariation>());
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
+ fonts.push_back(minikin::Font::Builder(font).build());
+ return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+ return Typeface::createFromFamilies(
+ std::vector<std::shared_ptr<minikin::FontFamily>>(
+ {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+ return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+ 0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+ // Create canvas
+ SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+ sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ SkiaCanvas canvas(skBitmap);
+
+ // Create minikin::Layout
+ std::unique_ptr<Typeface> typeface(makeTypeface());
+ minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, paint, f);
+ return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // the text is "abc"
+ DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+ EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is あいう in Japanese
+ DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is aいc. The only middle of the character is Japanese.
+ DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+ // We use the bottom, thicker line as underline. Here, use Noto's one.
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+} // namespace
diff --git a/location/Android.bp b/location/Android.bp
index cfe0e49..eb7cd01 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -39,3 +39,8 @@
],
},
}
+
+platform_compat_config {
+ name: "framework-location-compat-config",
+ src: ":framework-location",
+}
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index a792498..7aa9118 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "MediaProjectionTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index a4e5fb6..196b5c3 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -401,7 +401,9 @@
private void resetInternal() {
mSessionCallback = null;
- mPendingAppPrivateCommands.clear();
+ synchronized (mPendingAppPrivateCommands) {
+ mPendingAppPrivateCommands.clear();
+ }
if (mSession != null) {
setSessionSurface(null);
removeSessionOverlayView();
@@ -691,7 +693,10 @@
} else {
Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
+ "\" pending)");
- mPendingAppPrivateCommands.add(Pair.create(action, data));
+
+ synchronized (mPendingAppPrivateCommands) {
+ mPendingAppPrivateCommands.add(Pair.create(action, data));
+ }
}
}
@@ -1320,10 +1325,13 @@
mSession = session;
if (session != null) {
// Sends the pending app private commands first.
- for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
- mSession.sendAppPrivateCommand(command.first, command.second);
+
+ synchronized (mPendingAppPrivateCommands) {
+ for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
+ mSession.sendAppPrivateCommand(command.first, command.second);
+ }
+ mPendingAppPrivateCommands.clear();
}
- mPendingAppPrivateCommands.clear();
synchronized (sMainTvViewLock) {
if (hasWindowFocus() && TvView.this == sMainTvView.get()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ea773..49bd9d9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -345,6 +345,8 @@
<string name="accessibility_wifi_three_bars">Wifi three bars.</string>
<!-- Content description of the WIFI signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_wifi_signal_full">Wifi signal full.</string>
+ <!-- Content description of the WIFI signal when the WIFI is connected using the signal from a different device owned by the user. For accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
+ <string name="accessibility_wifi_other_device">Connected to your device.</string>
<!-- Content description of the Wi-Fi security type. This message indicates this is an open Wi-Fi (no password needed) [CHAR LIMIT=NONE] -->
<string name="accessibility_wifi_security_type_none">Open network</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index ee65ef4..ce466df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -50,6 +50,7 @@
};
public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+ public static final int WIFI_OTHER_DEVICE_CONNECTION = R.string.accessibility_wifi_other_device;
public static final int NO_CALLING = R.string.accessibility_no_calling;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 104f3d2..ee05f2d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,8 @@
"tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
"tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
"tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
+ "tests/src/com/android/systemui/qs/tiles/base/**/*.kt",
+ "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 5c2f979..0623d4a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -69,7 +69,7 @@
"exclude-annotation": "org.junit.Ignore"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 350b9c2..b3a7a8e9 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -158,26 +158,14 @@
BackHandler { onChangeScene(backScene) }
}
- Box(
- Modifier.drawWithContent {
- drawContent()
-
- // At this point, all scenes in scenesToCompose are fully laid out so they
- // are marked as ready. This is necessary because the animation code needs
- // to know the position and size of the elements in each scenes they are in,
- // so [readyScenes] will be used to decide whether the transition is ready
- // (see isTransitionReady() below).
- //
- // We can't do that in a DisposableEffect or SideEffect because those are
- // run between composition and layout. LaunchedEffect could work and might
- // be better, but it looks like launched effects run a frame later than this
- // code so doing this here seems better for performance.
- scenesToCompose.fastForEach { readyScenes[it.key] = true }
- }
- ) {
+ Box {
scenesToCompose.fastForEach { scene ->
val key = scene.key
key(key) {
+ // Mark this scene as ready once it has been composed, laid out and
+ // drawn the first time. We have to do this in a LaunchedEffect here
+ // because DisposableEffect runs between composition and layout.
+ LaunchedEffect(key) { readyScenes[key] = true }
DisposableEffect(key) { onDispose { readyScenes.remove(key) } }
scene.Content(
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 569dd4c..a51c55e 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -52,7 +52,7 @@
style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/cancel" />
+ android:text="@string/dismiss_dialog" />
<Space
android:layout_width="0dp"
@@ -64,6 +64,6 @@
style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/enable_display" />
+ android:text="@string/mirror_display" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 85fb3ac..587caaf 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -44,6 +44,6 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index e63229a..fc6d20e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -38,6 +38,6 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b6bca65..18f24ec 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -88,7 +88,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService)
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService)
</string>
<!-- The class path of the Safety Quick Settings Tile -->
@@ -604,7 +604,7 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">false</bool>
<!-- Whether we use large screen shade header which takes only one row compared to QS header -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5860806..a2637d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,10 @@
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
-
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
- <string name="enable_display">Enable display</string>
+ <string name="mirror_display">Mirror display</string>
+ <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+ <string name="dismiss_dialog">Dismiss</string>
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
index fb580ca..5344dcc 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
@@ -18,7 +18,7 @@
import android.hardware.biometrics.BiometricAuthenticator
-/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */
+/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI and Settings. */
enum class BiometricModality {
None,
Fingerprint,
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
index 39689ec..fdac37b 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.biometrics.shared.model
/**
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
new file mode 100644
index 0000000..a2b1198
--- /dev/null
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.shared.model
+
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+
+/** Fingerprint sensor property. Represents [FingerprintSensorPropertiesInternal]. */
+data class FingerprintSensor(
+ val sensorId: Int,
+ val sensorStrength: SensorStrength,
+ val maxEnrollmentsPerUser: Int,
+ val sensorType: FingerprintSensorType
+)
+
+/** Convert [FingerprintSensorPropertiesInternal] to corresponding [FingerprintSensor] */
+fun FingerprintSensorPropertiesInternal.toFingerprintSensor(): FingerprintSensor {
+ val sensorStrength: SensorStrength = this.sensorStrength.toSensorStrength()
+ val sensorType: FingerprintSensorType = this.sensorType.toSensorType()
+ return FingerprintSensor(this.sensorId, sensorStrength, this.maxEnrollmentsPerUser, sensorType)
+}
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index a9b4fe8..65c5a49 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.biometrics.shared.model
import android.graphics.Rect
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4b14d3cf..bf68869 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -37,9 +37,6 @@
* Various shared constants between Launcher and SysUI as part of quickstep
*/
public class QuickStepContract {
- // Fully qualified name of the Launcher activity.
- public static final String LAUNCHER_ACTIVITY_CLASS_NAME =
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity";
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
index cf83f62..31d78b9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
@@ -24,7 +24,8 @@
interface ISysuiUnlockAnimationController {
// Provides an implementation of the LauncherUnlockAnimationController to System UI, so that
// SysUI can use it to control the unlock animation in the launcher window.
- oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback);
+ oneway void setLauncherUnlockController(
+ String activityClass, ILauncherUnlockAnimationController callback);
// Called by Launcher whenever anything happens to change the state of its smartspace. System UI
// proactively saves this and uses it to perform the unlock animation without needing to make a
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b2287d87..51dafac 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -68,8 +69,6 @@
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -77,6 +76,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
@@ -84,6 +84,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -420,7 +421,7 @@
}
};
private final UserInteractor mUserInteractor;
- private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
+ private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
@@ -457,7 +458,7 @@
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
- Provider<AuthenticationInteractor> authenticationInteractor
+ Provider<DeviceEntryInteractor> deviceEntryInteractor
) {
super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -487,7 +488,7 @@
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mUserInteractor = userInteractor;
- mAuthenticationInteractor = authenticationInteractor;
+ mDeviceEntryInteractor = deviceEntryInteractor;
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mDeviceProvisionedController = deviceProvisionedController;
@@ -519,9 +520,9 @@
// When the scene framework says that the lockscreen has been dismissed, dismiss the
// keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mAuthenticationInteractor.get().isLockscreenDismissed(),
- isLockscreenDismissed -> {
- if (isLockscreenDismissed) {
+ mDeviceEntryInteractor.get().isDeviceEntered(),
+ isDeviceEntered -> {
+ if (isDeviceEntered) {
final int selectedUserId = mUserInteractor.getSelectedUserId();
showNextSecurityScreenOrFinish(
/* authenticated= */ true,
@@ -1081,15 +1082,11 @@
* one side).
*/
private boolean canUseOneHandedBouncer() {
- switch(mCurrentSecurityMode) {
- case PIN:
- case Pattern:
- case SimPin:
- case SimPuk:
- return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
- default:
- return false;
- }
+ return switch (mCurrentSecurityMode) {
+ case PIN, Pattern, SimPin, SimPuk -> getResources().getBoolean(
+ R.bool.can_use_one_handed_bouncer);
+ default -> false;
+ };
}
private boolean canDisplayUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index c095aa8..584357b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -80,8 +80,8 @@
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -205,7 +205,7 @@
private final Supplier<IWindowSession> mGlobalWindowSessionSupplier;
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private final MagnificationGestureDetector mGestureDetector;
- private final int mBounceEffectDuration;
+ private int mBounceEffectDuration;
private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
private Locale mLocale;
private NumberFormat mPercentFormat;
@@ -272,8 +272,8 @@
setupMagnificationSizeScaleOptions();
- mBounceEffectDuration = mResources.getInteger(
- com.android.internal.R.integer.config_shortAnimTime);
+ setBounceEffectDuration(mResources.getInteger(
+ com.android.internal.R.integer.config_shortAnimTime));
updateDimensions();
final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
@@ -1461,6 +1461,11 @@
mDragView.setColorFilter(filter);
}
+ @VisibleForTesting
+ void setBounceEffectDuration(int duration) {
+ mBounceEffectDuration = duration;
+ }
+
private void animateBounceEffect() {
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
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 b2433d4..80be008 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
@@ -29,9 +29,9 @@
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -59,18 +59,6 @@
/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
-
- /**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None); in such cases, the value of this
- * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
- * by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean>
-
/**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
@@ -129,14 +117,6 @@
/** Returns the length of the PIN or `0` if the current auth method is not PIN. */
suspend fun getPinLength(): Int
- /**
- * Returns whether the lockscreen is enabled.
- *
- * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
- * is considered not secure (for example, "swipe" is considered to be "none").
- */
- suspend fun isLockscreenEnabled(): Boolean
-
/** Reports an authentication attempt. */
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
@@ -167,6 +147,7 @@
suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}
+@SysUISingleton
class AuthenticationRepositoryImpl
@Inject
constructor(
@@ -174,20 +155,10 @@
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- keyguardRepository: KeyguardRepository,
private val lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
) : AuthenticationRepository {
- override val isUnlocked = keyguardRepository.isKeyguardUnlocked
-
- override suspend fun isLockscreenEnabled(): Boolean {
- return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUserId
- !lockPatternUtils.isLockScreenDisabled(selectedUserId)
- }
- }
-
override val isAutoConfirmEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = false,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 4cfc6aa..453a7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -26,9 +26,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -42,15 +40,19 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-/** Hosts application business logic related to authentication. */
+/**
+ * Hosts application business logic related to user authentication.
+ *
+ * Note: there is a distinction between authentication (determining a user's identity) and device
+ * entry (dismissing the lockscreen). For logic that is specific to device entry, please use
+ * `DeviceEntryInteractor` instead.
+ */
@SysUISingleton
class AuthenticationInteractor
@Inject
@@ -59,8 +61,7 @@
private val repository: AuthenticationRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- private val keyguardRepository: KeyguardRepository,
- sceneInteractor: SceneInteractor,
+ private val deviceEntryRepository: DeviceEntryRepository,
private val clock: SystemClock,
) {
/**
@@ -77,76 +78,13 @@
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
* the current authentication method is "swipe", the user does not need to complete any
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
- * get past it. This also means that the value of [isUnlocked] remains `false` even when the
- * lockscreen is showing and still needs to be dismissed by the user to proceed.
+ * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+ * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
- /**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
- * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
- * dismissed by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean> =
- combine(
- repository.isUnlocked,
- authenticationMethod,
- ) { isUnlocked, authenticationMethod ->
- !authenticationMethod.isSecure || isUnlocked
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
-
- /**
- * Whether the lockscreen has been dismissed (by any method). This can be false even when the
- * device is unlocked, e.g. when swipe to unlock is enabled.
- *
- * Note:
- * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
- * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
- * transition occurs).
- */
- val isLockscreenDismissed: StateFlow<Boolean> =
- sceneInteractor.desiredScene
- .map { it.key }
- .filter { currentScene ->
- currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
- }
- .map { it == SceneKey.Gone }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
- /**
- * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
- * authentication. This returns false whenever the lockscreen has been dismissed.
- *
- * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
- * UI.
- */
- val canSwipeToDismiss =
- combine(authenticationMethod, isLockscreenDismissed) {
- authenticationMethod,
- isLockscreenDismissed ->
- authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
- !isLockscreenDismissed
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
@@ -211,32 +149,15 @@
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
* the current authentication method is "swipe", the user does not need to complete any
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
- * get past it. This also means that the value of [isUnlocked] remains `false` even when the
- * lockscreen is showing and still needs to be dismissed by the user to proceed.
+ * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+ * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
return repository.getAuthenticationMethod().toDomainLayer()
}
/**
- * Returns `true` if the device currently requires authentication before content can be viewed;
- * `false` if content can be displayed without unlocking first.
- */
- suspend fun isAuthenticationRequired(): Boolean {
- return !isUnlocked.value && getAuthenticationMethod().isSecure
- }
-
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- fun isBypassEnabled(): Boolean {
- return keyguardRepository.isBypassEnabled()
- }
-
- /**
* Attempts to authenticate the user and unlock the device.
*
* If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -312,7 +233,7 @@
/** Starts refreshing the throttling state every second. */
private suspend fun startThrottlingCountdown() {
- cancelCountdown()
+ cancelThrottlingCountdown()
throttlingCountdownJob =
applicationScope.launch {
while (refreshThrottling() > 0) {
@@ -322,14 +243,14 @@
}
/** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
- private fun cancelCountdown() {
+ private fun cancelThrottlingCountdown() {
throttlingCountdownJob?.cancel()
throttlingCountdownJob = null
}
/** Notifies that the currently-selected user has changed. */
private suspend fun onSelectedUserChanged() {
- cancelCountdown()
+ cancelThrottlingCountdown()
if (refreshThrottling() > 0) {
startThrottlingCountdown()
}
@@ -378,7 +299,7 @@
DomainLayerAuthenticationMethodModel {
return when (this) {
is DataLayerAuthenticationMethodModel.None ->
- if (repository.isLockscreenEnabled()) {
+ if (deviceEntryRepository.isInsecureLockscreenEnabled()) {
DomainLayerAuthenticationMethodModel.Swipe
} else {
DomainLayerAuthenticationMethodModel.None
@@ -394,13 +315,10 @@
/** Result of a user authentication attempt. */
enum class AuthenticationResult {
- /** Authentication succeeded and the device is now unlocked. */
+ /** Authentication succeeded. */
SUCCEEDED,
- /** Authentication failed and the device remains unlocked. */
+ /** Authentication failed. */
FAILED,
- /**
- * Authentication was not performed, e.g. due to insufficient input, and the device remains
- * unlocked.
- */
+ /** Authentication was not performed, e.g. due to insufficient input. */
SKIPPED,
}
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 f3a463b..0c02369 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
@@ -26,6 +26,7 @@
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -50,6 +51,7 @@
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
private val repository: BouncerRepository,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
flags: SceneContainerFlags,
@@ -144,7 +146,7 @@
message: String? = null,
) {
applicationScope.launch {
- if (authenticationInteractor.isAuthenticationRequired()) {
+ if (deviceEntryInteractor.isAuthenticationRequired()) {
repository.setMessage(
message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
)
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 782ead3..c98cf31 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
@@ -62,6 +62,10 @@
initialValue = !bouncerInteractor.isThrottled.value,
)
+ // Handle to the scope of the child ViewModel (stored in [authMethod]).
+ private var childViewModelScope: CoroutineScope? = null
+ private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+
/** View-model for the current UI, based on the current authentication method. */
val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
authenticationInteractor.authenticationMethod
@@ -72,8 +76,31 @@
initialValue = null,
)
- // Handle to the scope of the child ViewModel (stored in [authMethod]).
- private var childViewModelScope: CoroutineScope? = null
+ /**
+ * A message for a throttling dialog to show when the user has attempted the wrong credential
+ * too many times and now must wait a while before attempting again.
+ *
+ * If `null`, no dialog should be shown.
+ *
+ * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
+ * dismisses this dialog.
+ */
+ val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+
+ /** The user-facing message to show in the bouncer. */
+ val message: StateFlow<MessageViewModel> =
+ combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
+ toMessageViewModel(message, isThrottled)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ toMessageViewModel(
+ message = bouncerInteractor.message.value,
+ isThrottled = bouncerInteractor.isThrottled.value,
+ ),
+ )
init {
if (flags.isEnabled()) {
@@ -98,33 +125,6 @@
}
}
- /** The user-facing message to show in the bouncer. */
- val message: StateFlow<MessageViewModel> =
- combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
- toMessageViewModel(message, isThrottled)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- toMessageViewModel(
- message = bouncerInteractor.message.value,
- isThrottled = bouncerInteractor.isThrottled.value,
- ),
- )
-
- private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
- /**
- * A message for a throttling dialog to show when the user has attempted the wrong credential
- * too many times and now must wait a while before attempting again.
- *
- * If `null`, no dialog should be shown.
- *
- * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
- * dismisses this dialog.
- */
- val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
-
/** Notifies that the emergency services button was clicked. */
fun onEmergencyServicesButtonClicked() {
// TODO(b/280877228): implement this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 9b93522..e8a8444 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,26 +18,25 @@
import android.app.Activity
import android.app.ActivityOptions
-import android.app.ActivityTaskManager
-import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.Dialog
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsets.Type
import android.view.WindowManager
import android.widget.ImageView
+import androidx.annotation.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.res.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.taskview.TaskView
/**
@@ -65,8 +64,8 @@
private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL"
}
- var detailTaskId = INVALID_TASK_ID
private lateinit var taskViewContainer: View
+ private lateinit var controlDetailRoot: View
private val taskWidthPercentWidth = activityContext.resources.getFloat(
R.dimen.controls_task_view_width_percentage
)
@@ -79,12 +78,7 @@
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
- fun removeDetailTask() {
- if (detailTaskId == INVALID_TASK_ID) return
- ActivityTaskManager.getInstance().removeTask(detailTaskId)
- detailTaskId = INVALID_TASK_ID
- }
-
+ @VisibleForTesting
val stateCallback = object : TaskView.Listener {
override fun onInitialized() {
taskViewContainer.apply {
@@ -98,33 +92,29 @@
activityContext,
0 /* enterResId */,
0 /* exitResId */
- ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ ).apply {
+ pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ taskAlwaysOnTop = true
+ }
taskView.startActivity(
pendingIntent,
fillInIntent,
options,
- getTaskViewBounds()
+ taskView.boundsOnScreen,
)
}
override fun onTaskRemovalStarted(taskId: Int) {
- detailTaskId = INVALID_TASK_ID
- dismiss()
+ taskView.release()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
- detailTaskId = taskId
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
setAlpha(1f)
}
}
-
- override fun onReleased() {
- removeDetailTask()
- }
-
override fun onBackPressedOnTaskRoot(taskId: Int) {
dismiss()
}
@@ -138,6 +128,9 @@
setContentView(R.layout.controls_detail_dialog)
taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container)
+ controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply {
+ setOnClickListener { _: View -> dismiss() }
+ }
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
addView(taskView)
@@ -147,13 +140,9 @@
requireViewById<ImageView>(R.id.control_detail_close).apply {
setOnClickListener { _: View -> dismiss() }
}
- requireViewById<View>(R.id.control_detail_root).apply {
- setOnClickListener { _: View -> dismiss() }
- }
requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
setOnClickListener { v: View ->
- removeDetailTask()
dismiss()
val action = ActivityStarter.OnDismissAction {
@@ -201,26 +190,9 @@
taskView.setListener(cvh.uiExecutor, stateCallback)
}
- fun getTaskViewBounds(): Rect {
- val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
- val windowMetrics = wm.getCurrentWindowMetrics()
- val rect = windowMetrics.bounds
- val metricInsets = windowMetrics.windowInsets
- val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars()
- or Type.displayCutout())
- val headerHeight = context.resources.getDimensionPixelSize(
- R.dimen.controls_detail_dialog_header_height)
-
- val finalRect = Rect(rect.left - insets.left /* left */,
- rect.top + insets.top + headerHeight /* top */,
- rect.right - insets.right /* right */,
- rect.bottom - insets.bottom /* bottom */)
- return finalRect
- }
-
override fun dismiss() {
if (!isShowing()) return
- taskView.release()
+ taskView.removeTask()
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 1b0d032..848c786 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -18,7 +18,6 @@
package com.android.systemui.controls.ui
import android.app.ActivityOptions
-import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
@@ -45,8 +44,6 @@
taskView.alpha = 0f
}
- private var detailTaskId = INVALID_TASK_ID
-
private val fillInIntent =
Intent().apply {
// Apply flags to make behaviour match documentLaunchMode=always.
@@ -57,7 +54,6 @@
private val stateCallback =
object : TaskView.Listener {
override fun onInitialized() {
-
val options =
ActivityOptions.makeCustomAnimation(
activityContext,
@@ -88,12 +84,10 @@
}
override fun onTaskRemovalStarted(taskId: Int) {
- detailTaskId = INVALID_TASK_ID
release()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
- detailTaskId = taskId
taskView.alpha = 1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index da5e933..04a9cae 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -25,6 +25,7 @@
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
+import android.app.IUriGrantsManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -688,4 +689,12 @@
static StatusBarManager provideStatusBarManager(Context context) {
return context.getSystemService(StatusBarManager.class);
}
+
+ @Provides
+ @Singleton
+ static IUriGrantsManager provideIUriGrantsManager() {
+ return IUriGrantsManager.Stub.asInterface(
+ ServiceManager.getService(Context.URI_GRANTS_SERVICE)
+ );
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 283a07b..4b6ad6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
@@ -173,6 +174,7 @@
ControlsModule.class,
CoroutinesModule.class,
DemoModeModule.class,
+ DeviceEntryModule.class,
DisableFlagsModule.class,
DisplayModule.class,
DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
new file mode 100644
index 0000000..e7f835f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.deviceentry
+
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ DeviceEntryRepositoryModule::class,
+ ],
+)
+object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
new file mode 100644
index 0000000..5b85ad0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.UserRepository
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Interface for classes that can access device-entry-related application state. */
+interface DeviceEntryRepository {
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method, unless in cases when the current
+ * authentication method is not "secure" (for example, None); in such cases, the value of this
+ * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
+ * by the user to proceed.
+ */
+ val isUnlocked: StateFlow<Boolean>
+
+ /**
+ * Whether the lockscreen should be shown when the authentication method is not secure (e.g.
+ * `None` or `Swipe`).
+ */
+ suspend fun isInsecureLockscreenEnabled(): Boolean
+
+ /**
+ * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
+ * dismissed once the authentication challenge is completed.
+ *
+ * This is a setting that is specific to the face unlock authentication method, because the user
+ * intent to unlock is not known. On devices that don't support face unlock, this always returns
+ * `true`.
+ *
+ * When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss
+ * the lockscreen.
+ */
+ fun isBypassEnabled(): Boolean
+}
+
+/** Encapsulates application state for device entry. */
+@SysUISingleton
+class DeviceEntryRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardBypassController: KeyguardBypassController,
+ keyguardStateController: KeyguardStateController,
+) : DeviceEntryRepository {
+
+ override val isUnlocked =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isUnlocked due to onUnlockedChanged"
+ )
+ }
+
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isUnlocked due to onKeyguardShowingChanged"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "initial isKeyguardUnlocked"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .stateIn(
+ applicationScope,
+ SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ override suspend fun isInsecureLockscreenEnabled(): Boolean {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.getSelectedUserInfo().id
+ !lockPatternUtils.isLockScreenDisabled(selectedUserId)
+ }
+ }
+
+ override fun isBypassEnabled() = keyguardBypassController.bypassEnabled
+
+ companion object {
+ private const val TAG = "DeviceEntryRepositoryImpl"
+ }
+}
+
+@Module
+interface DeviceEntryRepositoryModule {
+ @Binds fun repository(impl: DeviceEntryRepositoryImpl): DeviceEntryRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
new file mode 100644
index 0000000..5612c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -0,0 +1,110 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Hosts application business logic related to device entry.
+ *
+ * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless
+ * of the authentication method used.
+ */
+@SysUISingleton
+class DeviceEntryInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val repository: DeviceEntryRepository,
+ private val authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+) {
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method, unless in cases when the current
+ * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+ * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+ * dismissed by the user to proceed.
+ */
+ val isUnlocked: StateFlow<Boolean> =
+ combine(
+ repository.isUnlocked,
+ authenticationInteractor.authenticationMethod,
+ ) { isUnlocked, authenticationMethod ->
+ !authenticationMethod.isSecure || isUnlocked
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ /**
+ * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
+ * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away
+ * the non-secure lockscreen, even though they've already authenticated.
+ *
+ * Note: This does not imply that the lockscreen is visible or not.
+ */
+ val isDeviceEntered: StateFlow<Boolean> =
+ sceneInteractor.desiredScene
+ .map { it.key }
+ .filter { currentScene ->
+ currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+ }
+ .map { it == SceneKey.Gone }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Whether it's currently possible to swipe up to enter the device without requiring
+ * authentication. This returns `false` whenever the lockscreen has been dismissed.
+ *
+ * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+ * UI.
+ */
+ val canSwipeToEnter =
+ combine(authenticationInteractor.authenticationMethod, isDeviceEntered) {
+ authenticationMethod,
+ isDeviceEntered ->
+ authenticationMethod is AuthenticationMethodModel.Swipe && !isDeviceEntered
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Returns `true` if the device currently requires authentication before entry is granted;
+ * `false` if the device can be entered without authenticating first.
+ */
+ suspend fun isAuthenticationRequired(): Boolean {
+ return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure
+ }
+
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismissed once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled() = repository.isBypassEnabled()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 871257e..a00c3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -80,16 +80,9 @@
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
- /** Makes sure notification panel is updated before the user switch is complete. */
- // TODO(b/278873737): Tracking Bug
- @JvmField
- val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
- releasedFlag("load_notifications_before_the_user_switch_is_complete")
-
// TODO(b/277338665): Tracking Bug
@JvmField
- val NOTIFICATION_SHELF_REFACTOR =
- unreleasedFlag("notification_shelf_refactor", teamfood = true)
+ val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor")
// TODO(b/290787599): Tracking Bug
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d9b2c39..d89cf63 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -176,7 +176,6 @@
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
- SliderState.JUMP_BOOKEND_SELECTED -> executeOnBookend()
else -> {}
}
}
@@ -197,7 +196,7 @@
epsilon: Float = 0.00001f,
): Boolean {
val delta = abs(currentProgress - latestProgress)
- return abs(delta - config.jumpThreshold) < epsilon
+ return delta > config.jumpThreshold - epsilon
}
private fun bookendReached(currentProgress: Float): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
index 9c99c90..9f12f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
@@ -37,7 +37,9 @@
* Notification that the slider reached a certain progress on the slider track.
*
* This method is called in all intermediate steps of a continuous progress change as the slider
- * moves through the slider track.
+ * moves through the slider track. A single discrete movement of the handle by an external
+ * button or by a jump on the slider track will not trigger this callback. See
+ * [onSelectAndArrow] and [onProgressJump] for these cases.
*
* @param[progress] The progress of the slider in the range from 0F to 1F (inclusive).
*/
@@ -56,7 +58,7 @@
fun onProgressJump(@FloatRange(from = 0.0, to = 1.0) progress: Float)
/**
- * Notification that the slider handle was moved by a button press.
+ * Notification that the slider handle was moved discretely by one step via a button press.
*
* @param[progress] The progress of the slider in the range from 0F to 1F (inclusive).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index ff74050..2a69ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -44,7 +44,6 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
import com.android.systemui.shared.system.smartspace.SmartspaceState
@@ -219,6 +218,11 @@
*/
private var launcherUnlockController: ILauncherUnlockAnimationController? = null
+ /**
+ * Fully qualified class name of the launcher activity
+ */
+ private var launcherActivityClass: String? = null
+
private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
/**
@@ -226,7 +230,11 @@
* doesn't happen, we won't use in-window animations or the smartspace shared element
* transition, but that's okay!
*/
- override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) {
+ override fun setLauncherUnlockController(
+ activityClass: String,
+ callback: ILauncherUnlockAnimationController?
+ ) {
+ launcherActivityClass = activityClass
launcherUnlockController = callback
}
@@ -545,7 +553,6 @@
// gesture and the surface behind the keyguard should be made visible so that we can animate
// it in.
if (requestedShowSurfaceBehindKeyguard) {
-
// If we're flinging to dismiss here, it means the touch gesture ended in a fling during
// the time it takes the keyguard exit animation to start. This is an edge case race
// condition, which we handle by just playing a canned animation on the now-visible
@@ -785,7 +792,6 @@
if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
-
keyguardViewMediator.get().showSurfaceBehindKeyguard()
} else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
@@ -1113,17 +1119,18 @@
return playingCannedUnlockAnimation
}
+ /**
+ * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
+ * launcher or an app. If so, we can communicate with it to perform in-window/shared element
+ * transitions!
+ */
+ fun isNexusLauncherUnderneath(): Boolean {
+ return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
+ .runningTask?.topActivity?.className?.equals(it) }
+ ?: false
+ }
+
companion object {
- /**
- * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
- * launcher or an app. If so, we can communicate with it to perform in-window/shared element
- * transitions!
- */
- fun isNexusLauncherUnderneath(): Boolean {
- return ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(
- QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
- }
fun isFoldable(context: Context): Boolean {
return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b506a36..7e826b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -128,7 +128,6 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -146,6 +145,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -181,7 +181,6 @@
import java.util.function.Consumer;
import dagger.Lazy;
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -2833,7 +2832,7 @@
// playing in-window animations for this particular unlock since a previous unlock might
// have changed the Launcher state.
if (mWakeAndUnlocking
- && KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) {
+ && mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
@@ -3287,7 +3286,7 @@
// of the in-window animations are reflected. This is needed even if we're not actually
// playing in-window animations for this particular unlock since a previous unlock might
// have changed the Launcher state.
- if (KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) {
+ if (mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2557e81..36b93cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -47,7 +47,6 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -97,9 +96,6 @@
*/
val isKeyguardShowing: Flow<Boolean>
- /** Is the keyguard in a unlocked state? */
- val isKeyguardUnlocked: StateFlow<Boolean>
-
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -206,14 +202,6 @@
*/
fun isKeyguardShowing(): Boolean
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismissed once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- fun isBypassEnabled(): Boolean
-
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
@@ -265,7 +253,6 @@
screenLifecycle: ScreenLifecycle,
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
- private val keyguardBypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val dozeParameters: DozeParameters,
@@ -370,44 +357,6 @@
}
.distinctUntilChanged()
- override val isKeyguardUnlocked: StateFlow<Boolean> =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardStateController.Callback {
- override fun onUnlockedChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isKeyguardUnlocked due to onUnlockedChanged"
- )
- }
-
- override fun onKeyguardShowingChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
- )
- }
- }
-
- keyguardStateController.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "initial isKeyguardUnlocked"
- )
-
- awaitClose { keyguardStateController.removeCallback(callback) }
- }
- .distinctUntilChanged()
- .stateIn(
- scope,
- SharingStarted.Eagerly,
- initialValue = false,
- )
-
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -543,10 +492,6 @@
return keyguardStateController.isShowing
}
- override fun isBypassEnabled(): Boolean {
- return keyguardBypassController.bypassEnabled
- }
-
// TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
// [SceneInteractor] when scenes are ready.
override val statusBarState: StateFlow<StatusBarState> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 338f994..8063468 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,7 +23,6 @@
import android.graphics.Point
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.systemui.res.R
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -31,6 +30,7 @@
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -40,10 +40,10 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
@@ -79,6 +79,7 @@
private val commandQueue: CommandQueue,
featureFlags: FeatureFlags,
sceneContainerFlags: SceneContainerFlags,
+ deviceEntryRepository: DeviceEntryRepository,
bouncerRepository: KeyguardBouncerRepository,
configurationRepository: ConfigurationRepository,
shadeRepository: ShadeRepository,
@@ -168,7 +169,7 @@
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is unlocked or not. */
- val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+ val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -323,26 +324,7 @@
repository.setAnimateDozingTransitions(animate)
}
- fun isKeyguardDismissable(): Boolean {
- return repository.isKeyguardUnlocked.value
- }
-
companion object {
private const val TAG = "KeyguardInteractor"
-
- fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> true
- KeyguardState.DOZING -> true
- KeyguardState.DREAMING -> true
- KeyguardState.AOD -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> false
- KeyguardState.OCCLUDED -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 91b3357..c03e4d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,26 +34,22 @@
@Inject
constructor(
@Application applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
communalInteractor: CommunalInteractor,
val longPress: KeyguardLongPressViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
- authenticationInteractor.isUnlocked
+ deviceEntryInteractor.isUnlocked
.map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(authenticationInteractor.isUnlocked.value),
+ initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
)
private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) {
- SceneKey.Gone
- } else {
- SceneKey.Bouncer
- }
+ return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer
}
/** The key of the scene we should switch to when swiping left. */
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 ae3c912..ed6d41e 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
@@ -101,7 +101,8 @@
panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val logger: MediaViewLogger,
) {
/** Track the media player setting status on lock screen. */
@@ -1057,6 +1058,7 @@
// that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
}
+ logger.logMediaHostAttachment(currentAttachmentLocation)
if (isCrossFadeAnimatorRunning) {
// When cross-fading with an animation, we only notify the media carousel of the
// location change, once the view is reattached to the new place and not
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index 8f1595d..3ff23159 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -52,4 +52,8 @@
{ "location ($str1): $int1 -> $int2" }
)
}
+
+ fun logMediaHostAttachment(host: Int) {
+ buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 34d6233..128c237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -17,6 +17,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import android.app.IUriGrantsManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -31,6 +32,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.provider.Settings;
import android.service.quicksettings.IQSTileService;
@@ -43,11 +45,9 @@
import android.view.WindowManagerGlobal;
import android.widget.Switch;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -67,9 +67,10 @@
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
@@ -109,24 +110,30 @@
private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false);
private final TileServices mTileServices;
- private CustomTile(
- QSHost host,
+ private int mServiceUid = Process.INVALID_UID;
+
+ private final IUriGrantsManager mIUriGrantsManager;
+
+ @AssistedInject
+ CustomTile(
+ Lazy<QSHost> host,
QsEventLogger uiEventLogger,
- Looper backgroundLooper,
- Handler mainHandler,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- String action,
- Context userContext,
+ @Assisted String action,
+ @Assisted Context userContext,
CustomTileStatePersister customTileStatePersister,
TileServices tileServices,
- DisplayTracker displayTracker
+ DisplayTracker displayTracker,
+ IUriGrantsManager uriGrantsManager
) {
- super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
+ super(host.get(), uiEventLogger, backgroundLooper, mainHandler, falsingManager,
+ metricsLogger, statusBarStateController, activityStarter, qsLogger);
mTileServices = tileServices;
mWindowManager = WindowManagerGlobal.getWindowManagerService();
mComponent = ComponentName.unflattenFromString(action);
@@ -139,6 +146,7 @@
mService = mServiceManager.getTileService();
mCustomTileStatePersister = customTileStatePersister;
mDisplayTracker = displayTracker;
+ mIUriGrantsManager = uriGrantsManager;
}
@Override
@@ -268,7 +276,8 @@
*
* @param tile tile populated with state to apply
*/
- public void updateTileState(Tile tile) {
+ public void updateTileState(Tile tile, int appUid) {
+ mServiceUid = appUid;
// This comes from a binder call IQSService.updateQsTile
mHandler.post(() -> handleUpdateTileState(tile));
}
@@ -433,14 +442,25 @@
state.state = tileState;
Drawable drawable = null;
try {
- drawable = mTile.getIcon().loadDrawable(mUserContext);
+ drawable = mTile.getIcon().loadDrawableCheckingUriGrant(
+ mUserContext,
+ mIUriGrantsManager,
+ mServiceUid,
+ mComponent.getPackageName()
+ );
} catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state");
state.state = Tile.STATE_UNAVAILABLE;
- drawable = mDefaultIcon.loadDrawable(mUserContext);
}
- final Drawable drawableF = drawable;
+ final Drawable drawableF;
+ if (drawable != null) {
+ drawableF = drawable;
+ } else if (mDefaultIcon != null) {
+ drawableF = mDefaultIcon.loadDrawable(mUserContext);
+ } else {
+ drawableF = null;
+ }
state.iconSupplier = () -> {
if (drawableF == null) return null;
Drawable.ConstantState cs = drawableF.getConstantState();
@@ -543,96 +563,17 @@
/**
* Create a {@link CustomTile} for a given spec and user.
*
- * @param builder including injected common dependencies.
+ * @param factory including injected common dependencies.
* @param spec as provided by {@link CustomTile#toSpec}
* @param userContext context for the user that is creating this tile.
* @return a new {@link CustomTile}
*/
- public static CustomTile create(Builder builder, String spec, Context userContext) {
- return builder
- .setSpec(spec)
- .setUserContext(userContext)
- .build();
+ public static CustomTile create(Factory factory, String spec, Context userContext) {
+ return factory.create(getAction(spec), userContext);
}
- public static class Builder {
- final Lazy<QSHost> mQSHostLazy;
- final QsEventLogger mUiEventLogger;
- final Looper mBackgroundLooper;
- final Handler mMainHandler;
- private final FalsingManager mFalsingManager;
- final MetricsLogger mMetricsLogger;
- final StatusBarStateController mStatusBarStateController;
- final ActivityStarter mActivityStarter;
- final QSLogger mQSLogger;
- final CustomTileStatePersister mCustomTileStatePersister;
- private TileServices mTileServices;
- final DisplayTracker mDisplayTracker;
-
- Context mUserContext;
- String mSpec = "";
-
- @Inject
- public Builder(
- Lazy<QSHost> hostLazy,
- QsEventLogger uiEventLogger,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- CustomTileStatePersister customTileStatePersister,
- TileServices tileServices,
- DisplayTracker displayTracker
- ) {
- mQSHostLazy = hostLazy;
- mUiEventLogger = uiEventLogger;
- mBackgroundLooper = backgroundLooper;
- mMainHandler = mainHandler;
- mFalsingManager = falsingManager;
- mMetricsLogger = metricsLogger;
- mStatusBarStateController = statusBarStateController;
- mActivityStarter = activityStarter;
- mQSLogger = qsLogger;
- mCustomTileStatePersister = customTileStatePersister;
- mTileServices = tileServices;
- mDisplayTracker = displayTracker;
- }
-
- Builder setSpec(@NonNull String spec) {
- mSpec = spec;
- return this;
- }
-
- Builder setUserContext(@NonNull Context userContext) {
- mUserContext = userContext;
- return this;
- }
-
- @VisibleForTesting
- public CustomTile build() {
- if (mUserContext == null) {
- throw new NullPointerException("UserContext cannot be null");
- }
- String action = getAction(mSpec);
- return new CustomTile(
- mQSHostLazy.get(),
- mUiEventLogger,
- mBackgroundLooper,
- mMainHandler,
- mFalsingManager,
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQSLogger,
- action,
- mUserContext,
- mCustomTileStatePersister,
- mTileServices,
- mDisplayTracker
- );
- }
+ @AssistedFactory
+ public interface Factory {
+ CustomTile create(String action, Context userContext);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 1659c3e..c3c587d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.content.Context
import android.graphics.drawable.Icon
import android.view.ContextThemeWrapper
@@ -34,7 +35,7 @@
* Dialog to present to the user to ask for authorization to add a [TileService].
*/
class TileRequestDialog(
- context: Context
+ context: Context,
) : SystemUIDialog(context) {
companion object {
@@ -44,7 +45,7 @@
/**
* Set the data of the tile to add, to show the user.
*/
- fun setTileData(tileData: TileData) {
+ fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
val ll = (LayoutInflater
.from(context)
.inflate(R.layout.tile_service_request_dialog, null)
@@ -54,7 +55,7 @@
.getString(R.string.qs_tile_request_dialog_text, tileData.appName)
}
addView(
- createTileView(tileData),
+ createTileView(tileData, iUriGrantsManager),
context.resources.getDimensionPixelSize(
R.dimen.qs_tile_service_request_tile_width),
context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
@@ -65,13 +66,21 @@
setView(ll, spacing, spacing, spacing, spacing / 2)
}
- private fun createTileView(tileData: TileData): QSTileView {
+ private fun createTileView(
+ tileData: TileData,
+ iUriGrantsManager: IUriGrantsManager,
+ ): QSTileView {
val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
val tile = QSTileViewImpl(themedContext, true)
val state = QSTile.BooleanState().apply {
label = tileData.label
handlesLongClick = false
- icon = tileData.icon?.loadDrawable(context)?.let {
+ icon = tileData.icon?.loadDrawableCheckingUriGrant(
+ context,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )?.let {
QSTileImpl.DrawableIcon(it)
} ?: ResourceIcon.get(R.drawable.android)
contentDescription = label
@@ -93,8 +102,10 @@
* @property icon Icon for the tile.
*/
data class TileData(
+ val callingUid: Int,
val appName: CharSequence,
val label: CharSequence,
- val icon: Icon?
+ val icon: Icon?,
+ val packageName: String,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 899d0e2..08567af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.external
import android.app.Dialog
+import android.app.IUriGrantsManager
import android.app.StatusBarManager
import android.content.ComponentName
import android.content.DialogInterface
@@ -42,12 +43,13 @@
/**
* Controller to interface between [TileRequestDialog] and [QSHost].
*/
-class TileServiceRequestController constructor(
- private val qsHost: QSHost,
- private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry,
- private val eventLogger: TileRequestDialogEventLogger,
- private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+class TileServiceRequestController(
+ private val qsHost: QSHost,
+ private val commandQueue: CommandQueue,
+ private val commandRegistry: CommandRegistry,
+ private val eventLogger: TileRequestDialogEventLogger,
+ private val iUriGrantsManager: IUriGrantsManager,
+ private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
) {
companion object {
@@ -62,13 +64,14 @@
private val commandQueueCallback = object : CommandQueue.Callbacks {
override fun requestAddTile(
+ callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
icon: Icon,
callback: IAddTileResultCallback
) {
- requestTileAdd(componentName, appName, label, icon) {
+ requestTileAdd(callingUid, componentName, appName, label, icon) {
try {
callback.onTileRequest(it)
} catch (e: RemoteException) {
@@ -98,6 +101,7 @@
@VisibleForTesting
internal fun requestTileAdd(
+ callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
@@ -119,7 +123,13 @@
eventLogger.logUserResponse(response, packageName, instanceId)
callback.accept(response)
}
- val tileData = TileRequestDialog.TileData(appName, label, icon)
+ val tileData = TileRequestDialog.TileData(
+ callingUid,
+ appName,
+ label,
+ icon,
+ componentName.packageName,
+ )
createDialog(tileData, dialogResponse).also { dialog ->
dialogCanceller = {
if (packageName == it) {
@@ -143,7 +153,7 @@
}
}
return dialogCreator().apply {
- setTileData(tileData)
+ setTileData(tileData, iUriGrantsManager)
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
setOnCancelListener { responseHandler.accept(DISMISSED) }
@@ -168,7 +178,7 @@
Log.w(TAG, "Malformed componentName ${args[0]}")
return
}
- requestTileAdd(componentName, args[1], args[2], null) {
+ requestTileAdd(0, componentName, args[1], args[2], null) {
Log.d(TAG, "Response: $it")
}
}
@@ -192,14 +202,16 @@
@SysUISingleton
class Builder @Inject constructor(
private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry
+ private val commandRegistry: CommandRegistry,
+ private val iUriGrantsManager: IUriGrantsManager,
) {
fun create(qsHost: QSHost): TileServiceRequestController {
return TileServiceRequestController(
qsHost,
commandQueue,
commandRegistry,
- TileRequestDialogEventLogger()
+ TileRequestDialogEventLogger(),
+ iUriGrantsManager,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index c8691ac..fc24022 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -184,7 +184,7 @@
}
}
- private void verifyCaller(CustomTile tile) {
+ private int verifyCaller(CustomTile tile) {
try {
String packageName = tile.getComponent().getPackageName();
int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
@@ -192,6 +192,7 @@
if (Binder.getCallingUid() != uid) {
throw new SecurityException("Component outside caller's uid");
}
+ return uid;
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException(e);
}
@@ -228,7 +229,7 @@
public void updateQsTile(Tile tile, IBinder token) {
CustomTile customTile = getTileForToken(token);
if (customTile != null) {
- verifyCaller(customTile);
+ int uid = verifyCaller(customTile);
synchronized (mServices) {
final TileServiceManager tileServiceManager = mServices.get(customTile);
if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) {
@@ -239,7 +240,7 @@
tileServiceManager.clearPendingBind();
tileServiceManager.setLastUpdate(System.currentTimeMillis());
}
- customTile.updateTileState(tile);
+ customTile.updateTileState(tile, uid);
customTile.refreshState();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 21aaa94..b50798e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -32,6 +32,8 @@
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractorImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -61,6 +63,11 @@
): InstalledTilesComponentRepository
@Binds
+ abstract fun provideDisabledByPolicyInteractor(
+ impl: DisabledByPolicyInteractorImpl
+ ): DisabledByPolicyInteractor
+
+ @Binds
@IntoMap
@ClassKey(QSPipelineCoreStartable::class)
abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 632aa63..38bbce4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -51,15 +51,15 @@
protected final Map<String, Provider<QSTileImpl<?>>> mTileMap;
private final Lazy<QSHost> mQsHostLazy;
- private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
+ private final Provider<CustomTile.Factory> mCustomTileFactoryProvider;
@Inject
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
- Provider<CustomTile.Builder> customTileBuilderProvider,
+ Provider<CustomTile.Factory> customTileFactoryProvider,
Map<String, Provider<QSTileImpl<?>>> tileMap) {
mQsHostLazy = qsHostLazy;
- mCustomTileBuilderProvider = customTileBuilderProvider;
+ mCustomTileFactoryProvider = customTileFactoryProvider;
mTileMap = tileMap;
}
@@ -87,7 +87,7 @@
// Custom tiles
if (tileSpec.startsWith(CustomTile.PREFIX)) {
return CustomTile.create(
- mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
+ mCustomTileFactoryProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
// Broken tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
index dc9e115..9d10072 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.actions
import android.content.Intent
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
new file mode 100644
index 0000000..056f967
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Provides restrictions data for the tiles. This is used in
+ * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is
+ * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy].
+ */
+interface DisabledByPolicyInteractor {
+
+ /**
+ * Checks if the tile is restricted by the policy for a specific user. Pass the result to the
+ * [handlePolicyResult] to let the user know that the tile is disable by the admin.
+ */
+ suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult
+
+ /**
+ * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this
+ * method. No further handling is required and the input event can be skipped at this point.
+ *
+ * Returns false when [policyResult] is [PolicyResult.TileEnabled] and this method has done
+ * nothing.
+ */
+ fun handlePolicyResult(policyResult: PolicyResult): Boolean
+
+ sealed interface PolicyResult {
+ /** Tile has no policy restrictions. */
+ data object TileEnabled : PolicyResult
+
+ /**
+ * Tile is disabled by policy. Pass this to [DisabledByPolicyInteractor.handlePolicyResult]
+ * to show the user info screen using
+ * [RestrictedLockUtils.getShowAdminSupportDetailsIntent].
+ */
+ data class TileDisabled(val admin: EnforcedAdmin) : PolicyResult
+ }
+}
+
+@SysUISingleton
+class DisabledByPolicyInteractorImpl
+@Inject
+constructor(
+ private val context: Context,
+ private val activityStarter: ActivityStarter,
+ private val restrictedLockProxy: RestrictedLockProxy,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : DisabledByPolicyInteractor {
+
+ override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult =
+ withContext(backgroundDispatcher) {
+ val admin: EnforcedAdmin =
+ restrictedLockProxy.getEnforcedAdmin(userId, userRestriction)
+ ?: return@withContext PolicyResult.TileEnabled
+
+ return@withContext if (
+ !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction)
+ ) {
+ PolicyResult.TileDisabled(admin)
+ } else {
+ PolicyResult.TileEnabled
+ }
+ }
+
+ override fun handlePolicyResult(policyResult: PolicyResult): Boolean =
+ when (policyResult) {
+ is PolicyResult.TileEnabled -> false
+ is PolicyResult.TileDisabled -> {
+ val intent =
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+ context,
+ policyResult.admin
+ )
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ true
+ }
+ }
+}
+
+/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */
+@VisibleForTesting
+class RestrictedLockProxy @Inject constructor(private val context: Context) {
+
+ @WorkerThread
+ fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean =
+ RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context,
+ userRestriction,
+ userId,
+ )
+
+ @WorkerThread
+ fun getEnforcedAdmin(userId: Int, userRestriction: String?): EnforcedAdmin? =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context,
+ userRestriction,
+ userId,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
index 1a03481..7a22e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
index 8289704..0aa6b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
data class QSTileDataRequest(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
index d6c9705..2bc6643 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import androidx.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 8569fc7..14fc639 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import android.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
index ed7ec8e..ffe38dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index bb4de80..58a335e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -1,8 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.viewmodel
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import com.android.internal.util.Preconditions
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -10,10 +28,13 @@
import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.util.kotlin.sample
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@@ -25,6 +46,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -37,40 +59,35 @@
* Provides a hassle-free way to implement new tiles according to current System UI architecture
* standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
* [QSTileLifecycle.ALIVE] state.
+ *
+ * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
*/
-abstract class BaseQSTileViewModel<DATA_TYPE>
+class BaseQSTileViewModel<DATA_TYPE>
@VisibleForTesting
constructor(
- final override val config: QSTileConfig,
+ override val config: QSTileConfig,
private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val tileScope: CoroutineScope,
) : QSTileViewModel {
- /**
- * @param config contains all the static information (like TileSpec) about the tile.
- * @param userActionInteractor encapsulates user input processing logic. Use it to start
- * activities, show dialogs or otherwise update the tile state.
- * @param tileDataInteractor provides [DATA_TYPE] and its availability.
- * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call
- * interactors methods. This should likely to be @Background CoroutineDispatcher.
- * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer.
- * It's called in [backgroundDispatcher], so it's safe to perform long running operations
- * there.
- */
+ @AssistedInject
constructor(
- config: QSTileConfig,
- userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
- tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
- mapper: QSTileDataToStateMapper<DATA_TYPE>,
- backgroundDispatcher: CoroutineDispatcher,
+ @Assisted config: QSTileConfig,
+ @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+ @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+ @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ @Background backgroundDispatcher: CoroutineDispatcher,
) : this(
config,
userActionInteractor,
tileDataInteractor,
mapper,
+ disabledByPolicyInteractor,
backgroundDispatcher,
CoroutineScope(SupervisorJob())
)
@@ -145,7 +162,7 @@
userIds
.flatMapLatest { userId ->
merge(
- userInputFlow(),
+ userInputFlow(userId),
forceUpdates.map { StateUpdateTrigger.ForceUpdate },
)
.onStart { emit(StateUpdateTrigger.InitialRequest) }
@@ -172,14 +189,41 @@
replay = 1, // we only care about the most recent value
)
- private fun userInputFlow(): Flow<StateUpdateTrigger> {
+ private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
data class StateWithData<T>(val state: QSTileState, val data: T)
+ return when (config.policy) {
+ is QSTilePolicy.NoRestrictions -> userInputs
+ is QSTilePolicy.Restricted ->
+ userInputs.filter {
+ val result =
+ disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
+ !disabledByPolicyInteractor.handlePolicyResult(result)
+ }
// Skip the input until there is some data
- return userInputs.sample(
- state.combine(tileData) { state, data -> StateWithData(state, data) }
- ) { input, stateWithData ->
+ }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
+ input,
+ stateWithData ->
StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
}
}
+
+ interface Factory<T> {
+
+ /**
+ * @param config contains all the static information (like TileSpec) about the tile.
+ * @param userActionInteractor encapsulates user input processing logic. Use it to start
+ * activities, show dialogs or otherwise update the tile state.
+ * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+ * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
+ * layer. It's called in [backgroundDispatcher], so it's safe to perform long running
+ * operations there.
+ */
+ fun create(
+ config: QSTileConfig,
+ userActionInteractor: QSTileUserActionInteractor<T>,
+ tileDataInteractor: QSTileDataInteractor<T>,
+ mapper: QSTileDataToStateMapper<T>,
+ ): BaseQSTileViewModel<T>
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 3fedbfc..d0809c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.di
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 019d3c0..1a6cf99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
import androidx.annotation.StringRes
@@ -10,4 +26,19 @@
val tileIcon: Icon,
@StringRes val tileLabelRes: Int,
val instanceId: InstanceId,
+ val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
)
+
+/** Represents policy restrictions that may be imposed on the tile. */
+sealed interface QSTilePolicy {
+ /** Tile has no policy restrictions */
+ data object NoRestrictions : QSTilePolicy
+
+ /**
+ * Tile might be disabled by policy. [userRestriction] is usually a constant from
+ * [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE].
+ * [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used
+ * to resolve this and show user a message when needed.
+ */
+ data class Restricted(val userRestriction: String) : QSTilePolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
index 1d5c1bc..6d7c576 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
enum class QSTileLifecycle {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index dc5c690..0ccde74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
import android.service.quicksettings.Tile
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index 0b232c2..a145042 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
import android.view.View
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index d66d0a1..e5cb7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
import kotlinx.coroutines.flow.SharedFlow
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 d4bdb77..f6299e3 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
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.viewmodel
import android.content.Context
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 722d366..a3499bd 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
@@ -26,6 +26,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
@@ -63,6 +64,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val authenticationInteractor: AuthenticationInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val flags: SceneContainerFlags,
@@ -119,7 +121,7 @@
/** Switches between scenes based on ever-changing application state. */
private fun automaticallySwitchScenes() {
applicationScope.launch {
- authenticationInteractor.isUnlocked
+ deviceEntryInteractor.isUnlocked
.mapNotNull { isUnlocked ->
val renderedScenes =
when (val transitionState = sceneInteractor.transitionState.value) {
@@ -130,7 +132,6 @@
transitionState.toScene,
)
}
- val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
when {
@@ -141,7 +142,7 @@
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
renderedScenes.contains(SceneKey.Lockscreen) ->
- if (isBypassEnabled) {
+ if (deviceEntryInteractor.isBypassEnabled()) {
SceneKey.Gone to
"device unlocked in Lockscreen scene with bypass"
} else {
@@ -191,7 +192,7 @@
}
WakefulnessState.STARTING_TO_WAKE -> {
val authMethod = authenticationInteractor.getAuthenticationMethod()
- val isUnlocked = authenticationInteractor.isUnlocked.value
+ val isUnlocked = deviceEntryInteractor.isUnlocked.value
when {
authMethod == AuthenticationMethodModel.None -> {
switchToScene(
@@ -241,7 +242,7 @@
/** Collects and reports signals into the falsing system. */
private fun collectFalsingSignals() {
applicationScope.launch {
- authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+ deviceEntryInteractor.isDeviceEntered.collect { isLockscreenDismissed ->
if (isLockscreenDismissed) {
falsingCollector.onSuccessfulUnlock()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d5934e6..1038c67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -114,7 +114,6 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
@@ -163,6 +162,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -393,7 +393,6 @@
private TrackingStartedListener mTrackingStartedListener;
private OpenCloseListener mOpenCloseListener;
private GestureRecorder mGestureRecorder;
- private boolean mPanelExpanded;
private boolean mKeyguardQsUserSwitchEnabled;
private boolean mKeyguardUserSwitcherEnabled;
@@ -1321,7 +1320,7 @@
// when we switch between split shade and regular shade we want to enforce setting qs to
// the default state: expanded for split shade and collapsed otherwise
- if (!isKeyguardShowing() && mPanelExpanded) {
+ if (!isKeyguardShowing() && isPanelExpanded()) {
mQsController.setExpanded(mSplitShadeEnabled);
}
if (isKeyguardShowing() && mQsController.getExpanded() && mSplitShadeEnabled) {
@@ -2630,10 +2629,9 @@
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
- if (mPanelExpanded != isExpanded) {
- mPanelExpanded = isExpanded;
+ if (isPanelExpanded() != isExpanded) {
+ setExpandedOrAwaitingInputTransfer(isExpanded);
updateSystemUiStateFlags();
- mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(mPanelExpanded);
mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
if (!isExpanded) {
mQsController.closeQsCustomizer();
@@ -2641,9 +2639,13 @@
}
}
+ private void setExpandedOrAwaitingInputTransfer(boolean expandedOrAwaitingInputTransfer) {
+ mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expandedOrAwaitingInputTransfer);
+ }
+
@Override
public boolean isPanelExpanded() {
- return mPanelExpanded;
+ return mShadeRepository.getLegacyExpandedOrAwaitingInputTransfer().getValue();
}
private int calculatePanelHeightShade() {
@@ -3392,7 +3394,7 @@
ipw.print("mMaxAllowedKeyguardNotifications=");
ipw.println(mMaxAllowedKeyguardNotifications);
ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
- ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded);
+ ipw.print("isPanelExpanded()="); ipw.println(isPanelExpanded());
ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
ipw.print("mDozing="); ipw.println(mDozing);
@@ -3606,7 +3608,7 @@
+ isFullyExpanded() + " inQs=" + mQsController.getExpanded());
}
mSysUiState
- .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, mPanelExpanded)
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, isPanelExpanded())
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !mQsController.getExpanded())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index c803e6f..6f5e41f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.IActivityManager;
import android.content.Context;
@@ -48,7 +49,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
@@ -58,7 +58,9 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -71,6 +73,8 @@
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
@@ -108,6 +112,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private final Executor mBackgroundExecutor;
private final AuthController mAuthController;
+ private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
private boolean mHasTopUi;
@@ -151,6 +156,7 @@
ScreenOffAnimationController screenOffAnimationController,
AuthController authController,
ShadeExpansionStateManager shadeExpansionStateManager,
+ Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeWindowLogger logger) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -171,12 +177,12 @@
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
+ mShadeInteractorLazy = shadeInteractorLazy;
((SysuiStatusBarStateController) statusBarStateController)
.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
@@ -224,9 +230,9 @@
}
@VisibleForTesting
- void onShadeExpansionFullyChanged(Boolean isExpanded) {
- if (mCurrentState.panelExpanded != isExpanded) {
- mCurrentState.panelExpanded = isExpanded;
+ void onShadeOrQsExpanded(Boolean isExpanded) {
+ if (mCurrentState.shadeOrQsExpanded != isExpanded) {
+ mCurrentState.shadeOrQsExpanded = isExpanded;
apply(mCurrentState);
}
}
@@ -289,6 +295,11 @@
public void fetchWindowRootView() {
WindowRootViewComponent component = mWindowRootViewComponentFactory.create();
mWindowRootView = component.getWindowRootView();
+ collectFlow(
+ mWindowRootView,
+ mShadeInteractorLazy.get().isAnyExpanded(),
+ this::onShadeOrQsExpanded
+ );
}
@Override
@@ -384,7 +395,7 @@
}
private void applyFocusableFlag(NotificationShadeWindowState state) {
- boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+ boolean panelFocusable = state.notificationShadeFocusable && state.shadeOrQsExpanded;
if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
|| ENABLE_REMOTE_INPUT && state.remoteInputActive
// Make the panel focusable if we're doing the screen off animation, since the light
@@ -408,7 +419,7 @@
}
private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
- if (state.panelExpanded || state.bouncerShowing
+ if (state.shadeOrQsExpanded || state.bouncerShowing
|| ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
} else {
@@ -544,7 +555,7 @@
state.keyguardOccluded,
state.keyguardNeedsInput,
state.panelVisible,
- state.panelExpanded,
+ state.shadeOrQsExpanded,
state.notificationShadeFocusable,
state.bouncerShowing,
state.keyguardFadingAway,
@@ -582,7 +593,7 @@
mCurrentState.keyguardGoingAway,
mCurrentState.bouncerShowing,
mCurrentState.dozing,
- mCurrentState.panelExpanded,
+ mCurrentState.shadeOrQsExpanded,
mCurrentState.dreaming);
}
}
@@ -833,7 +844,7 @@
*/
@Override
public boolean getPanelExpanded() {
- return mCurrentState.panelExpanded;
+ return mCurrentState.shadeOrQsExpanded;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index e3010ca..fbe164a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -32,7 +32,7 @@
@JvmField var keyguardNeedsInput: Boolean = false,
@JvmField var panelVisible: Boolean = false,
/** shade panel is expanded (expansion fraction > 0) */
- @JvmField var panelExpanded: Boolean = false,
+ @JvmField var shadeOrQsExpanded: Boolean = false,
@JvmField var notificationShadeFocusable: Boolean = false,
@JvmField var bouncerShowing: Boolean = false,
@JvmField var keyguardFadingAway: Boolean = false,
@@ -70,7 +70,7 @@
keyguardOccluded.toString(),
keyguardNeedsInput.toString(),
panelVisible.toString(),
- panelExpanded.toString(),
+ shadeOrQsExpanded.toString(),
notificationShadeFocusable.toString(),
bouncerShowing.toString(),
keyguardFadingAway.toString(),
@@ -137,7 +137,7 @@
this.keyguardOccluded = keyguardOccluded
this.keyguardNeedsInput = keyguardNeedsInput
this.panelVisible = panelVisible
- this.panelExpanded = panelExpanded
+ this.shadeOrQsExpanded = panelExpanded
this.notificationShadeFocusable = notificationShadeFocusable
this.bouncerShowing = bouncerShowing
this.keyguardFadingAway = keyguardFadingAway
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 068d5a5..9c5a201 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.shade.ui.viewmodel
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,15 +34,15 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
private val bouncerInteractor: BouncerInteractor,
val shadeHeaderViewModel: ShadeHeaderViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
combine(
- authenticationInteractor.isUnlocked,
- authenticationInteractor.canSwipeToDismiss,
+ deviceEntryInteractor.isUnlocked,
+ deviceEntryInteractor.canSwipeToEnter,
) { isUnlocked, canSwipeToDismiss ->
upDestinationSceneKey(
isUnlocked = isUnlocked,
@@ -54,8 +54,8 @@
started = SharingStarted.WhileSubscribed(),
initialValue =
upDestinationSceneKey(
- isUnlocked = authenticationInteractor.isUnlocked.value,
- canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
+ isUnlocked = deviceEntryInteractor.isUnlocked.value,
+ canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 93bb435..664103f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -442,6 +442,7 @@
* @see IStatusBar#requestAddTile
*/
default void requestAddTile(
+ int callingUid,
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@NonNull CharSequence label,
@@ -1314,6 +1315,7 @@
@Override
public void requestAddTile(
+ int callingUid,
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@NonNull CharSequence label,
@@ -1326,6 +1328,7 @@
args.arg3 = label;
args.arg4 = icon;
args.arg5 = callback;
+ args.arg6 = callingUid;
mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget();
}
@@ -1772,8 +1775,9 @@
CharSequence label = (CharSequence) args.arg3;
Icon icon = (Icon) args.arg4;
IAddTileResultCallback callback = (IAddTileResultCallback) args.arg5;
+ int callingUid = (int) args.arg6;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).requestAddTile(
+ mCallbacks.get(i).requestAddTile(callingUid,
componentName, appName, label, icon, callback);
}
args.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index d908243..93c55de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -24,8 +24,6 @@
String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
= "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
- boolean shouldAllowLockscreenRemoteInput();
-
/**
* @param userId user Id
* @return true if we re on a secure lock screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index ea5ca27..2147510 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,8 +49,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -63,14 +61,14 @@
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.settings.SecureSettings;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Handles keeping track of the current user, profiles, and various things related to hiding
* contents, redacting notifications, and the lockscreen.
@@ -81,8 +79,6 @@
NotificationLockscreenUserManager,
StateListener {
private static final String TAG = "LockscreenUserManager";
- private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
-
private final DeviceProvisionedController mDeviceProvisionedController;
private final KeyguardStateController mKeyguardStateController;
private final SecureSettings mSecureSettings;
@@ -103,9 +99,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
- private final FeatureFlags mFeatureFlags;
private boolean mShowLockscreenNotifications;
- private boolean mAllowLockscreenRemoteInput;
private LockPatternUtils mLockPatternUtils;
protected KeyguardManager mKeyguardManager;
private int mState = StatusBarState.SHADE;
@@ -181,22 +175,7 @@
protected final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
- public void onUserChanged(int newUser, @NonNull Context userContext) {
- if (!mFeatureFlags.isEnabled(
- Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
- handleUserChange(newUser);
- }
- }
-
- @Override
public void onUserChanging(int newUser, @NonNull Context userContext) {
- if (mFeatureFlags.isEnabled(
- Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
- handleUserChange(newUser);
- }
- }
-
- private void handleUserChange(int newUser) {
mCurrentUserId = newUser;
updateCurrentProfilesCache();
@@ -239,8 +218,7 @@
KeyguardStateController keyguardStateController,
SecureSettings secureSettings,
DumpManager dumpManager,
- LockPatternUtils lockPatternUtils,
- FeatureFlags featureFlags) {
+ LockPatternUtils lockPatternUtils) {
mContext = context;
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
@@ -258,7 +236,6 @@
mDeviceProvisionedController = deviceProvisionedController;
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
- mFeatureFlags = featureFlags;
dumpManager.registerDumpable(this);
}
@@ -305,14 +282,6 @@
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
- false,
- mSettingsObserver,
- UserHandle.USER_ALL);
- }
-
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
null /* handler */, UserHandle.ALL);
@@ -343,10 +312,6 @@
return mShowLockscreenNotifications;
}
- public boolean shouldAllowLockscreenRemoteInput() {
- return mAllowLockscreenRemoteInput;
- }
-
public boolean isCurrentProfile(int userId) {
synchronized (mLock) {
return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
@@ -357,10 +322,6 @@
mShowLockscreenNotifications = show;
}
- private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
- mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
- }
-
protected void updateLockscreenNotificationSetting() {
final boolean show = mSecureSettings.getIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
@@ -372,19 +333,6 @@
& DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
setShowLockscreenNotifications(show && allowedByDpm);
-
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- final boolean remoteInput = mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
- 0,
- mCurrentUserId) != 0;
- final boolean remoteInputDpm =
- (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
- setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
- } else {
- setLockscreenAllowRemoteInput(false);
- }
}
/**
@@ -472,7 +420,6 @@
mUsersAllowingNotifications.append(userHandle, allowed);
return allowed;
}
-
return mUsersAllowingNotifications.get(userHandle);
}
@@ -639,37 +586,6 @@
}
}
-// public void updatePublicMode() {
-// //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
-// // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
-// // asking if the keyguard is showing. We still need to check it though because showing the
-// // camera on the keyguard has a state of SHADE but the keyguard is still showing.
-// final boolean showingKeyguard = mState != StatusBarState.SHADE
-// || mKeyguardStateController.isShowing();
-// final boolean devicePublic = showingKeyguard && isSecure(getCurrentUserId());
-//
-//
-// // Look for public mode users. Users are considered public in either case of:
-// // - device keyguard is shown in secure mode;
-// // - profile is locked with a work challenge.
-// SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
-// for (int i = currentProfiles.size() - 1; i >= 0; i--) {
-// final int userId = currentProfiles.valueAt(i).id;
-// boolean isProfilePublic = devicePublic;
-// if (!devicePublic && userId != getCurrentUserId()) {
-// // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge
-// // due to a race condition where this code could be called before
-// // TrustManagerService updates its internal records, resulting in an incorrect
-// // state being cached in mLockscreenPublicMode. (b/35951989)
-// if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-// && isSecure(userId)) {
-// isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
-// }
-// }
-// setLockscreenPublicMode(isProfilePublic, userId);
-// }
-// }
-
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationLockscreenUserManager state:");
@@ -677,8 +593,6 @@
pw.println(mCurrentUserId);
pw.print(" mShowLockscreenNotifications=");
pw.println(mShowLockscreenNotifications);
- pw.print(" mAllowLockscreenRemoteInput=");
- pw.println(mAllowLockscreenRemoteInput);
pw.print(" mCurrentProfiles=");
synchronized (mLock) {
for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index befc64d..d4b6dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -482,9 +482,6 @@
private boolean showBouncerForRemoteInput(View view, PendingIntent pendingIntent,
ExpandableNotificationRow row) {
- if (mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
- return false;
- }
final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
index 10e7573..718c1c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsNotificationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -14,9 +11,6 @@
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index d089252..64dfc6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -167,11 +167,7 @@
* @see Vibrator#getPrimitiveDurations(int...)
*/
public int[] getPrimitiveDurations(int... primitiveIds) {
- if (!hasVibrator()) {
- return new int[]{0};
- } else {
- return mVibrator.getPrimitiveDurations(primitiveIds);
- }
+ return mVibrator.getPrimitiveDurations(primitiveIds);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index d1464ed..249c831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,13 +17,16 @@
package com.android.systemui.statusbar.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
/**
* A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -49,4 +52,15 @@
@IntoMap
@ClassKey(OngoingCallController::class)
abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(LightBarController::class)
+ abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+
+ @Binds
+ @IntoSet
+ abstract fun statusBarInitializedListener(
+ statusBarModeRepository: StatusBarModeRepository,
+ ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt
new file mode 100644
index 0000000..0cd31d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.model
+
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.statusbar.phone.BoundsPair
+
+/** Keeps track of various parameters coordinating the appearance of the status bar. */
+data class StatusBarAppearance(
+ /** The current mode of the status bar. */
+ val mode: StatusBarMode,
+ /** The current bounds of the status bar. */
+ val bounds: BoundsPair,
+ /**
+ * A list of appearance regions for the appearance of the status bar background. Used to
+ * determine the correct coloring of status bar icons to ensure contrast. See
+ * [com.android.systemui.statusbar.phone.LightBarController].
+ */
+ val appearanceRegions: List<AppearanceRegion>,
+ /**
+ * The navigation bar color as set by
+ * [com.android.systemui.statusbar.CommandQueue.onSystemBarAttributesChanged].
+ *
+ * TODO(b/277764509): This likely belongs in a "NavigationBarAppearance"-type class, not a
+ * status bar class.
+ */
+ val navbarColorManagedByIme: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
new file mode 100644
index 0000000..747efe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.model
+
+import com.android.systemui.statusbar.phone.BarTransitions
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode
+
+/**
+ * The possible status bar modes.
+ *
+ * See the associated [BarTransitions] mode documentation for information about each of the modes
+ * and how they're used.
+ */
+enum class StatusBarMode {
+ /** Use a semi-transparent (aka translucent) background for the status bar. */
+ SEMI_TRANSPARENT,
+ /**
+ * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode
+ * can be requested by apps). See
+ * [com.android.systemui.statusbar.phone.LightsOutNotifController].
+ */
+ LIGHTS_OUT,
+ /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */
+ LIGHTS_OUT_TRANSPARENT,
+ /** Use an opaque background for the status bar. */
+ OPAQUE,
+ /** Use a transparent background for the status bar. */
+ TRANSPARENT;
+
+ /** Converts a [StatusBarMode] to its [BarTransitions] integer. */
+ @TransitionMode
+ fun toTransitionModeInt(): Int {
+ return when (this) {
+ SEMI_TRANSPARENT -> MODE_SEMI_TRANSPARENT
+ LIGHTS_OUT -> MODE_LIGHTS_OUT
+ LIGHTS_OUT_TRANSPARENT -> MODE_LIGHTS_OUT_TRANSPARENT
+ OPAQUE -> MODE_OPAQUE
+ TRANSPARENT -> MODE_TRANSPARENT
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 9d73071..2b05994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.data.repository
+import android.graphics.Rect
import android.view.WindowInsets
import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
+import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
+import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.CoreStartable
@@ -25,12 +30,22 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.data.model.StatusBarAppearance
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.BoundsPair
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -41,7 +56,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModeRepository {
+interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -62,6 +77,16 @@
val isInFullscreenMode: StateFlow<Boolean>
/**
+ * The current status bar appearance parameters.
+ *
+ * Null at system startup, but non-null once the first system callback has been received.
+ */
+ val statusBarAppearance: StateFlow<StatusBarAppearance?>
+
+ /** The current mode of the status bar. */
+ val statusBarMode: StateFlow<StatusBarMode>
+
+ /**
* Requests for the status bar to be shown transiently.
*
* TODO(b/277764509): Don't allow [CentralSurfaces] to set the transient mode; have it
@@ -85,6 +110,8 @@
@Application scope: CoroutineScope,
@DisplayId thisDisplayId: Int,
private val commandQueue: CommandQueue,
+ private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
+ ongoingCallRepository: OngoingCallRepository,
) : StatusBarModeRepository, CoreStartable {
private val commandQueueCallback =
@@ -114,7 +141,7 @@
override fun onSystemBarAttributesChanged(
displayId: Int,
- @WindowInsetsController.Appearance appearance: Int,
+ @Appearance appearance: Int,
appearanceRegions: Array<AppearanceRegion>,
navbarColorManagedByIme: Boolean,
@WindowInsetsController.Behavior behavior: Int,
@@ -125,7 +152,11 @@
if (displayId != thisDisplayId) return
_originalStatusBarAttributes.value =
StatusBarAttributes(
+ appearance,
+ appearanceRegions.toList(),
+ navbarColorManagedByIme,
requestedVisibleTypes,
+ letterboxDetails.toList(),
)
}
}
@@ -139,6 +170,19 @@
private val _originalStatusBarAttributes = MutableStateFlow<StatusBarAttributes?>(null)
+ private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect()))
+
+ override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+ val statusBarBoundsProvider = component.boundsProvider
+ val listener =
+ object : StatusBarBoundsProvider.BoundsChangeListener {
+ override fun onStatusBarBoundsChanged(bounds: BoundsPair) {
+ _statusBarBounds.value = bounds
+ }
+ }
+ statusBarBoundsProvider.addChangeListener(listener)
+ }
+
override val isInFullscreenMode: StateFlow<Boolean> =
_originalStatusBarAttributes
.map { params ->
@@ -148,6 +192,89 @@
}
.stateIn(scope, SharingStarted.Eagerly, false)
+ /** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */
+ private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> =
+ combine(
+ _originalStatusBarAttributes,
+ _statusBarBounds,
+ ) { originalAttributes, statusBarBounds ->
+ if (originalAttributes == null) {
+ null
+ } else {
+ val (newAppearance, newAppearanceRegions) =
+ modifyAppearanceIfNeeded(
+ originalAttributes.appearance,
+ originalAttributes.appearanceRegions,
+ originalAttributes.letterboxDetails,
+ statusBarBounds,
+ )
+ ModifiedStatusBarAttributes(
+ newAppearance,
+ newAppearanceRegions,
+ originalAttributes.navbarColorManagedByIme,
+ statusBarBounds,
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
+
+ override val statusBarAppearance: StateFlow<StatusBarAppearance?> =
+ combine(
+ modifiedStatusBarAttributes,
+ isTransientShown,
+ isInFullscreenMode,
+ ongoingCallRepository.hasOngoingCall,
+ ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
+ if (modifiedAttributes == null) {
+ null
+ } else {
+ val statusBarMode =
+ toBarMode(
+ modifiedAttributes.appearance,
+ isTransientShown,
+ isInFullscreenMode,
+ hasOngoingCall,
+ )
+ StatusBarAppearance(
+ statusBarMode,
+ modifiedAttributes.statusBarBounds,
+ modifiedAttributes.appearanceRegions,
+ modifiedAttributes.navbarColorManagedByIme,
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
+
+ override val statusBarMode: StateFlow<StatusBarMode> =
+ statusBarAppearance
+ .map { it?.mode ?: StatusBarMode.TRANSPARENT }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = StatusBarMode.TRANSPARENT)
+
+ private fun toBarMode(
+ appearance: Int,
+ isTransientShown: Boolean,
+ isInFullscreenMode: Boolean,
+ hasOngoingCall: Boolean,
+ ): StatusBarMode {
+ return when {
+ hasOngoingCall && isInFullscreenMode -> StatusBarMode.SEMI_TRANSPARENT
+ isTransientShown -> StatusBarMode.SEMI_TRANSPARENT
+ else -> appearance.toBarMode()
+ }
+ }
+
+ @Appearance
+ private fun Int.toBarMode(): StatusBarMode {
+ val lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS
+ return when {
+ this and lightsOutOpaque == lightsOutOpaque -> StatusBarMode.LIGHTS_OUT
+ this and APPEARANCE_LOW_PROFILE_BARS != 0 -> StatusBarMode.LIGHTS_OUT_TRANSPARENT
+ this and APPEARANCE_OPAQUE_STATUS_BARS != 0 -> StatusBarMode.OPAQUE
+ this and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS != 0 -> StatusBarMode.SEMI_TRANSPARENT
+ else -> StatusBarMode.TRANSPARENT
+ }
+ }
+
override fun showTransient() {
_isTransientShown.value = true
}
@@ -156,11 +283,54 @@
_isTransientShown.value = false
}
+ private fun modifyAppearanceIfNeeded(
+ appearance: Int,
+ appearanceRegions: List<AppearanceRegion>,
+ letterboxDetails: List<LetterboxDetails>,
+ statusBarBounds: BoundsPair,
+ ): Pair<Int, List<AppearanceRegion>> =
+ if (shouldUseLetterboxAppearance(letterboxDetails)) {
+ val letterboxAppearance =
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ appearance,
+ appearanceRegions,
+ letterboxDetails,
+ statusBarBounds,
+ )
+ Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
+ } else {
+ Pair(appearance, appearanceRegions)
+ }
+
+ private fun shouldUseLetterboxAppearance(letterboxDetails: List<LetterboxDetails>) =
+ letterboxDetails.isNotEmpty()
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}")
+ pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}")
+ pw.println("statusBarMode: ${statusBarMode.value}")
+ }
+
/**
* Internal class keeping track of the raw status bar attributes received from the callback.
* Should never be exposed.
*/
private data class StatusBarAttributes(
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
@WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
+ val letterboxDetails: List<LetterboxDetails>,
+ )
+
+ /**
+ * Internal class keeping track of how [StatusBarAttributes] were transformed into new
+ * attributes based on letterboxing and other factors. Should never be exposed.
+ */
+ private data class ModifiedStatusBarAttributes(
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
+ val statusBarBounds: BoundsPair,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 3e1f09f..90cba40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -290,10 +290,6 @@
void acquireGestureWakeLock(long time);
- boolean setAppearance(int appearance);
-
- int getBarMode();
-
void resendMessage(int msg);
void resendMessage(Object msg);
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 f7ff39c..22b9298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -36,16 +36,11 @@
import android.util.Slog;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
-import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsController.Appearance;
-import android.view.WindowInsetsController.Behavior;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.statusbar.LetterboxDetails;
-import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.res.R;
import com.android.systemui.assist.AssistManager;
@@ -109,7 +104,6 @@
private final UserTracker mUserTracker;
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
- private final SystemBarAttributesListener mSystemBarAttributesListener;
private final ActivityStarter mActivityStarter;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
@@ -149,7 +143,6 @@
Optional<Vibrator> vibratorOptional,
DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
- SystemBarAttributesListener systemBarAttributesListener,
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker,
QSHost qsHost,
@@ -187,7 +180,6 @@
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
mVibratorOptional, resources);
- mSystemBarAttributesListener = systemBarAttributesListener;
mActivityStarter = activityStarter;
}
@@ -457,29 +449,6 @@
mCentralSurfaces.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, running);
}
-
- @Override
- public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
- AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
- LetterboxDetails[] letterboxDetails) {
- if (displayId != mDisplayId) {
- return;
- }
- // SystemBarAttributesListener should __always__ be the top-level listener for system bar
- // attributes changed.
- mSystemBarAttributesListener.onSystemBarAttributesChanged(
- displayId,
- appearance,
- appearanceRegions,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- letterboxDetails
- );
- }
-
@Override
public void toggleKeyboardShortcutsMenu(int deviceId) {
mCentralSurfaces.resendMessage(new CentralSurfaces.KeyboardShortcutsMessage(deviceId));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 3cb5e1f..7dc4b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -85,8 +85,6 @@
override fun getRotation() = 0
override fun setBarStateForTest(state: Int) {}
override fun acquireGestureWakeLock(time: Long) {}
- override fun setAppearance(appearance: Int) = false
- override fun getBarMode() = 0
override fun resendMessage(msg: Int) {}
override fun resendMessage(msg: Any?) {}
override fun setLastCameraLaunchSource(source: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 05beded..3e2f10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -21,9 +21,6 @@
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
-import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
@@ -33,12 +30,6 @@
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -92,7 +83,6 @@
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -208,6 +198,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.data.model.StatusBarMode;
import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -222,7 +213,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -329,21 +319,6 @@
}
@Override
- public boolean setAppearance(int appearance) {
- if (mAppearance != appearance) {
- mAppearance = appearance;
- return updateBarMode(barMode(isTransientShown(), appearance));
- }
-
- return false;
- }
-
- @Override
- public int getBarMode() {
- return mStatusBarMode;
- }
-
- @Override
public void resendMessage(int msg) {
mMessageRouter.cancelMessages(msg);
mMessageRouter.sendMessage(msg);
@@ -464,7 +439,6 @@
private final UserInfoControllerImpl mUserInfoControllerImpl;
private final DemoModeController mDemoModeController;
private final NotificationsController mNotificationsController;
- private final OngoingCallController mOngoingCallController;
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
@@ -497,9 +471,6 @@
private final Provider<FingerprintManager> mFingerprintManager;
private final ActivityStarter mActivityStarter;
- /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
- private @Appearance int mAppearance;
-
private final DisplayMetrics mDisplayMetrics;
// XXX: gesture research
@@ -554,7 +525,6 @@
private final DelayableExecutor mMainExecutor;
private int mInteractingWindows;
- private @TransitionMode int mStatusBarMode;
private final ViewMediatorCallback mKeyguardViewMediatorCallback;
private final ScrimController mScrimController;
@@ -714,7 +684,6 @@
BrightnessSliderController.Factory brightnessSliderFactory,
ScreenOffAnimationController screenOffAnimationController,
WallpaperController wallpaperController,
- OngoingCallController ongoingCallController,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
FeatureFlags featureFlags,
@@ -820,7 +789,6 @@
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
mWallpaperController = wallpaperController;
- mOngoingCallController = ongoingCallController;
mStatusBarSignalPolicy = statusBarSignalPolicy;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mFeatureFlags = featureFlags;
@@ -854,9 +822,6 @@
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mActivityLaunchAnimator = activityLaunchAnimator;
- // The status bar background may need updating when the ongoing call status changes.
- mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode());
-
// TODO(b/190746471): Find a better home for this.
DateTimeView.setReceiverHandler(timeTickHandler);
@@ -1192,8 +1157,8 @@
mJavaAdapter.alwaysCollectFlow(
mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged);
mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.isInFullscreenMode(),
- this::onStatusBarFullscreenChanged);
+ mStatusBarModeRepository.getStatusBarMode(),
+ this::updateBarMode);
mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
mCommandQueue.addCallback(mCommandQueueCallbacks);
@@ -1712,50 +1677,12 @@
if (transientShown) {
mNoAnimationOnNextBarModeChange = true;
}
- maybeUpdateBarMode();
}
- private void onStatusBarFullscreenChanged(boolean isWindowShown) {
- maybeUpdateBarMode();
- }
-
- private void maybeUpdateBarMode() {
- final int barMode = barMode(isTransientShown(), mAppearance);
- if (updateBarMode(barMode)) {
- mLightBarController.onStatusBarModeChanged(barMode);
- updateBubblesVisibility();
- }
- }
-
- private boolean updateBarMode(int barMode) {
- if (mStatusBarMode != barMode) {
- mStatusBarMode = barMode;
- checkBarModes();
- mAutoHideController.touchAutoHide();
- return true;
- }
- return false;
- }
-
- private @TransitionMode int barMode(boolean isTransient, int appearance) {
- boolean isFullscreen = mStatusBarModeRepository.isInFullscreenMode().getValue();
- final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
- if (mOngoingCallController.hasOngoingCall() && isFullscreen) {
- // Force show the status bar if there's an ongoing call.
- return MODE_SEMI_TRANSPARENT;
- } else if (isTransient) {
- return MODE_SEMI_TRANSPARENT;
- } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
- return MODE_LIGHTS_OUT;
- } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) {
- return MODE_LIGHTS_OUT_TRANSPARENT;
- } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) {
- return MODE_OPAQUE;
- } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) {
- return MODE_SEMI_TRANSPARENT;
- } else {
- return MODE_TRANSPARENT;
- }
+ private void updateBarMode(StatusBarMode barMode) {
+ checkBarModes();
+ mAutoHideController.touchAutoHide();
+ updateBubblesVisibility();
}
@Override
@@ -1785,7 +1712,10 @@
public void checkBarModes() {
if (mDemoModeController.isInDemoMode()) return;
if (mStatusBarTransitions != null) {
- checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions);
+ checkBarMode(
+ mStatusBarModeRepository.getStatusBarMode().getValue(),
+ mStatusBarWindowState,
+ mStatusBarTransitions);
}
mNavigationBarController.checkNavBarModes(mDisplayId);
mNoAnimationOnNextBarModeChange = false;
@@ -1794,16 +1724,19 @@
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
+ StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
- mStatusBarMode != MODE_LIGHTS_OUT
- && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT));
+ mode != StatusBarMode.LIGHTS_OUT
+ && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT));
}
- void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
+ void checkBarMode(
+ StatusBarMode mode,
+ @WindowVisibleState int windowState,
BarTransitions transitions) {
final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
&& windowState != WINDOW_STATE_HIDDEN;
- transitions.transitionTo(mode, anim);
+ transitions.transitionTo(mode.toTransitionModeInt(), anim);
}
private void finishBarAnimations() {
@@ -1850,8 +1783,6 @@
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
pw.print(" mStatusBarWindowState=");
pw.println(windowStateToString(mStatusBarWindowState));
- pw.print(" mStatusBarMode=");
- pw.println(BarTransitions.modeToString(mStatusBarMode));
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index a61914a..231a8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
+import android.content.Context
import android.graphics.Rect
import android.view.InsetsFlags
import android.view.ViewDebug
@@ -29,20 +30,17 @@
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import java.io.PrintWriter
-import java.util.Arrays
import javax.inject.Inject
-class LetterboxAppearance(
+data class LetterboxAppearance(
@Appearance val appearance: Int,
- val appearanceRegions: Array<AppearanceRegion>
+ val appearanceRegions: List<AppearanceRegion>,
) {
override fun toString(): String {
val appearanceString =
ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
- return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+ return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
}
}
@@ -54,69 +52,81 @@
class LetterboxAppearanceCalculator
@Inject
constructor(
- private val lightBarController: LightBarController,
+ context: Context,
dumpManager: DumpManager,
private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
-) : OnStatusBarViewInitializedListener, Dumpable {
+) : Dumpable {
+
+ private val darkAppearanceIconColor = context.getColor(
+ // For a dark background status bar, use a *light* icon color.
+ com.android.settingslib.R.color.light_mode_icon_color_single_tone
+ )
+ private val lightAppearanceIconColor = context.getColor(
+ // For a light background status bar, use a *dark* icon color.
+ com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+ )
init {
dumpManager.registerCriticalDumpable(this)
}
- private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
-
private var lastAppearance: Int? = null
- private var lastAppearanceRegions: Array<AppearanceRegion>? = null
- private var lastLetterboxes: Array<LetterboxDetails>? = null
+ private var lastAppearanceRegions: List<AppearanceRegion>? = null
+ private var lastLetterboxes: List<LetterboxDetails>? = null
private var lastLetterboxAppearance: LetterboxAppearance? = null
fun getLetterboxAppearance(
@Appearance originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>,
- letterboxes: Array<LetterboxDetails>
+ originalAppearanceRegions: List<AppearanceRegion>,
+ letterboxes: List<LetterboxDetails>,
+ statusBarBounds: BoundsPair,
): LetterboxAppearance {
lastAppearance = originalAppearance
lastAppearanceRegions = originalAppearanceRegions
lastLetterboxes = letterboxes
return getLetterboxAppearanceInternal(
- letterboxes, originalAppearance, originalAppearanceRegions)
+ letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
.also { lastLetterboxAppearance = it }
}
private fun getLetterboxAppearanceInternal(
- letterboxes: Array<LetterboxDetails>,
+ letterboxes: List<LetterboxDetails>,
originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>
+ originalAppearanceRegions: List<AppearanceRegion>,
+ statusBarBounds: BoundsPair,
): LetterboxAppearance {
- if (isScrimNeeded(letterboxes)) {
+ if (isScrimNeeded(letterboxes, statusBarBounds)) {
return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
}
val appearance = appearanceWithoutScrim(originalAppearance)
val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
- return LetterboxAppearance(appearance, appearanceRegions.toTypedArray())
+ return LetterboxAppearance(appearance, appearanceRegions)
}
- private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean {
+ private fun isScrimNeeded(
+ letterboxes: List<LetterboxDetails>,
+ statusBarBounds: BoundsPair,
+ ): Boolean {
if (isOuterLetterboxMultiColored()) {
return true
}
return letterboxes.any { letterbox ->
- letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) ||
- letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds())
+ letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.start) ||
+ letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.end)
}
}
private fun getAppearanceRegions(
- originalAppearanceRegions: Array<AppearanceRegion>,
- letterboxes: Array<LetterboxDetails>
+ originalAppearanceRegions: List<AppearanceRegion>,
+ letterboxes: List<LetterboxDetails>
): List<AppearanceRegion> {
return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
getAllOuterAppearanceRegions(letterboxes)
}
private fun sanitizeAppearanceRegions(
- originalAppearanceRegions: Array<AppearanceRegion>,
- letterboxes: Array<LetterboxDetails>
+ originalAppearanceRegions: List<AppearanceRegion>,
+ letterboxes: List<LetterboxDetails>
): List<AppearanceRegion> =
originalAppearanceRegions.map { appearanceRegion ->
val matchingLetterbox =
@@ -134,7 +144,7 @@
private fun originalAppearanceWithScrim(
@Appearance originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>
+ originalAppearanceRegions: List<AppearanceRegion>
): LetterboxAppearance {
return LetterboxAppearance(
originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
@@ -146,7 +156,7 @@
originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()
private fun getAllOuterAppearanceRegions(
- letterboxes: Array<LetterboxDetails>
+ letterboxes: List<LetterboxDetails>
): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()
private fun getOuterAppearanceRegions(
@@ -172,11 +182,9 @@
private fun getOuterAppearance(): Int {
val backgroundColor = outerLetterboxBackgroundColor()
val darkAppearanceContrast =
- ContrastColorUtil.calculateContrast(
- lightBarController.darkAppearanceIconColor, backgroundColor)
+ ContrastColorUtil.calculateContrast(darkAppearanceIconColor, backgroundColor)
val lightAppearanceContrast =
- ContrastColorUtil.calculateContrast(
- lightBarController.lightAppearanceIconColor, backgroundColor)
+ ContrastColorUtil.calculateContrast(lightAppearanceIconColor, backgroundColor)
return if (lightAppearanceContrast > darkAppearanceContrast) {
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
} else {
@@ -193,18 +201,6 @@
return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
}
- private fun getEndSideIconsBounds(): Rect {
- return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect()
- }
-
- private fun getStartSideIconBounds(): Rect {
- return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect()
- }
-
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
- statusBarBoundsProvider = component.boundsProvider
- }
-
private fun Rect.overlapsWith(other: Rect): Boolean {
if (this.contains(other) || other.contains(this)) {
return false
@@ -216,8 +212,8 @@
pw.println(
"""
lastAppearance: ${lastAppearance?.toAppearanceString()}
- lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
- lastLetterboxes: ${Arrays.toString(lastLetterboxes)},
+ lastAppearanceRegion: $lastAppearanceRegions,
+ lastLetterboxes: $lastLetterboxes,
lastLetterboxAppearance: $lastLetterboxAppearance
""".trimIndent())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
index 61377e2..2e3f0d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
@@ -18,12 +18,10 @@
package com.android.systemui.statusbar.phone
import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
@Module
abstract class LetterboxModule {
@@ -31,10 +29,4 @@
@IntoMap
@ClassKey(LetterboxBackgroundProvider::class)
abstract fun bindFeature(impl: LetterboxBackgroundProvider): CoreStartable
-
- @Binds
- @IntoSet
- abstract fun statusBarInitializedListener(
- letterboxAppearanceCalculator: LetterboxAppearanceCalculator
- ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index c2dd059..4d3e2ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -22,7 +22,6 @@
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import android.annotation.ColorInt;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
@@ -31,17 +30,22 @@
import android.view.WindowInsetsController.Appearance;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
+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.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -52,7 +56,8 @@
* Controls how light status bar flag applies to the icons.
*/
@SysUISingleton
-public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
+public class LightBarController implements
+ BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
private static final String TAG = "LightBarController";
private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -60,18 +65,19 @@
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
+ private final JavaAdapter mJavaAdapter;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
+ private final StatusBarModeRepository mStatusBarModeRepository;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
private @Appearance int mAppearance;
private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
private int mStatusBarMode;
+ private BoundsPair mStatusBarBounds = new BoundsPair(new Rect(), new Rect());
private int mNavigationBarMode;
private int mNavigationMode;
- private final int mDarkIconColor;
- private final int mLightIconColor;
/**
* Whether the navigation bar should be light factoring in already how much alpha the scrim has.
@@ -116,18 +122,18 @@
@Inject
public LightBarController(
Context ctx,
+ JavaAdapter javaAdapter,
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
+ StatusBarModeRepository statusBarModeRepository,
DumpManager dumpManager,
DisplayTracker displayTracker) {
- mDarkIconColor = ctx.getColor(
- com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
- mLightIconColor = ctx.getColor(
- com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ mJavaAdapter = javaAdapter;
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
mBatteryController.addCallback(this);
+ mStatusBarModeRepository = statusBarModeRepository;
mNavigationMode = navModeController.addListener((mode) -> {
mNavigationMode = mode;
});
@@ -137,14 +143,11 @@
}
}
- @ColorInt
- int getLightAppearanceIconColor() {
- return mDarkIconColor;
- }
-
- @ColorInt
- int getDarkAppearanceIconColor() {
- return mLightIconColor;
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarModeRepository.getStatusBarAppearance(),
+ this::onStatusBarAppearanceChanged);
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -157,26 +160,48 @@
mBiometricUnlockController = biometricUnlockController;
}
- void onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged,
- int statusBarMode, boolean navbarColorManagedByIme) {
+ private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
+ if (params == null) {
+ return;
+ }
+ int newStatusBarMode = params.getMode().toTransitionModeInt();
+ boolean sbModeChanged = mStatusBarMode != newStatusBarMode;
+ mStatusBarMode = newStatusBarMode;
+
+ boolean sbBoundsChanged = !mStatusBarBounds.equals(params.getBounds());
+ mStatusBarBounds = params.getBounds();
+
+ onStatusBarAppearanceChanged(
+ params.getAppearanceRegions().toArray(new AppearanceRegion[0]),
+ sbModeChanged,
+ sbBoundsChanged,
+ params.getNavbarColorManagedByIme());
+ }
+
+ private void onStatusBarAppearanceChanged(
+ AppearanceRegion[] appearanceRegions,
+ boolean sbModeChanged,
+ boolean sbBoundsChanged,
+ boolean navbarColorManagedByIme) {
final int numStacks = appearanceRegions.length;
boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks;
for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) {
stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]);
}
- if (stackAppearancesChanged || sbModeChanged || mIsCustomizingForBackNav) {
+
+ if (stackAppearancesChanged
+ || sbModeChanged
+ // Be sure to re-draw when the status bar bounds have changed because the status bar
+ // icons may have moved to be part of a different appearance region. See b/301605450
+ || sbBoundsChanged
+ || mIsCustomizingForBackNav) {
mAppearanceRegions = appearanceRegions;
- onStatusBarModeChanged(statusBarMode);
+ updateStatus(mAppearanceRegions);
mIsCustomizingForBackNav = false;
}
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
- void onStatusBarModeChanged(int newBarMode) {
- mStatusBarMode = newBarMode;
- updateStatus(mAppearanceRegions);
- }
-
public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
int navigationBarMode, boolean navbarColorManagedByIme) {
int diff = appearance ^ mAppearance;
@@ -224,7 +249,10 @@
}
private void reevaluate() {
- onStatusBarAppearanceChanged(mAppearanceRegions, true /* sbModeChange */, mStatusBarMode,
+ onStatusBarAppearanceChanged(
+ mAppearanceRegions,
+ /* sbModeChanged= */ true,
+ /* sbBoundsChanged= */ true,
mNavbarColorManagedByIme);
onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
mNavigationBarMode, mNavbarColorManagedByIme);
@@ -444,31 +472,43 @@
* Injectable factory for creating a {@link LightBarController}.
*/
public static class Factory {
+ private final JavaAdapter mJavaAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
private final BatteryController mBatteryController;
private final NavigationModeController mNavModeController;
+ private final StatusBarModeRepository mStatusBarModeRepository;
private final DumpManager mDumpManager;
private final DisplayTracker mDisplayTracker;
@Inject
public Factory(
+ JavaAdapter javaAdapter,
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
+ StatusBarModeRepository statusBarModeRepository,
DumpManager dumpManager,
DisplayTracker displayTracker) {
-
+ mJavaAdapter = javaAdapter;
mDarkIconDispatcher = darkIconDispatcher;
mBatteryController = batteryController;
mNavModeController = navModeController;
+ mStatusBarModeRepository = statusBarModeRepository;
mDumpManager = dumpManager;
mDisplayTracker = displayTracker;
}
/** Create an {@link LightBarController} */
public LightBarController create(Context context) {
- return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
- mNavModeController, mDumpManager, mDisplayTracker);
+ return new LightBarController(
+ context,
+ mJavaAdapter,
+ mDarkIconDispatcher,
+ mBatteryController,
+ mNavModeController,
+ mStatusBarModeRepository,
+ mDumpManager,
+ mDisplayTracker);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
index f5ba399..00b08f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -22,22 +22,34 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.util.ListenerSet
import com.android.systemui.util.boundsOnScreen
import javax.inject.Inject
import javax.inject.Named
-/** Provides various bounds within the status bar. */
+/**
+ * Provides the bounds of the **content** on each side of the status bar.
+ *
+ * This is distinct from [StatusBarContentInsetsProvider], which provides the bounds of full status
+ * bar after accounting for system insets.
+ */
@StatusBarFragmentScope
class StatusBarBoundsProvider
@Inject
constructor(
- private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>,
@Named(START_SIDE_CONTENT) private val startSideContent: View,
@Named(END_SIDE_CONTENT) private val endSideContent: View,
) : StatusBarFragmentComponent.Startable {
interface BoundsChangeListener {
- fun onStatusBarBoundsChanged()
+ fun onStatusBarBoundsChanged(bounds: BoundsPair)
+ }
+
+ private val changeListeners = ListenerSet<BoundsChangeListener>()
+
+ fun addChangeListener(listener: BoundsChangeListener) {
+ changeListeners.addIfAbsent(listener)
+ listener.onStatusBarBoundsChanged(previousBounds)
}
private var previousBounds =
@@ -48,7 +60,7 @@
val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds)
if (previousBounds != newBounds) {
previousBounds = newBounds
- changeListeners.forEach { it.onStatusBarBoundsChanged() }
+ changeListeners.forEach { it.onStatusBarBoundsChanged(newBounds) }
}
}
@@ -89,4 +101,10 @@
get() = startSideContent.boundsOnScreen
}
-private data class BoundsPair(val start: Rect, val end: Rect)
+/**
+ * Stores bounds of the status content.
+ *
+ * @property start the bounds of the status bar content on the start side (clock & notif icons).
+ * @property end the bounds of the status bar content on the end side (system icons & battery).
+ */
+data class BoundsPair(val start: Rect, val end: Rect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8d86d72..eedf35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.view.WindowInsets.Type.navigationBars;
+
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -66,7 +67,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
@@ -134,7 +134,7 @@
// dranw its first frame.
private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
- private static String TAG = "StatusBarKeyguardViewManager";
+ private static final String TAG = "StatusBarKeyguardViewManager";
private static final boolean DEBUG = false;
protected final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
deleted file mode 100644
index 829577b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.view.InsetsFlags
-import android.view.ViewDebug
-import android.view.WindowInsets.Type.InsetsType
-import android.view.WindowInsetsController.Appearance
-import android.view.WindowInsetsController.Behavior
-import com.android.internal.statusbar.LetterboxDetails
-import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
-import javax.inject.Inject
-
-/**
- * Top-level listener of system attributes changed. This class is __always the first__ one to be
- * notified about changes.
- *
- * It is responsible for modifying any attributes if necessary, and then notifying the other
- * downstream listeners.
- */
-@SysUISingleton
-class SystemBarAttributesListener
-@Inject
-internal constructor(
- private val centralSurfaces: CentralSurfaces,
- private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
- private val lightBarController: LightBarController,
- dumpManager: DumpManager,
-) : Dumpable, StatusBarBoundsProvider.BoundsChangeListener {
-
- private var lastLetterboxAppearance: LetterboxAppearance? = null
- private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null
-
- init {
- dumpManager.registerCriticalDumpable(this)
- }
-
- override fun onStatusBarBoundsChanged() {
- val params = lastSystemBarAttributesParams
- if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) {
- onSystemBarAttributesChanged(
- params.displayId,
- params.appearance,
- params.appearanceRegionsArray,
- params.navbarColorManagedByIme,
- params.behavior,
- params.requestedVisibleTypes,
- params.packageName,
- params.letterboxesArray)
- }
- }
-
- fun onSystemBarAttributesChanged(
- displayId: Int,
- @Appearance originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>,
- navbarColorManagedByIme: Boolean,
- @Behavior behavior: Int,
- @InsetsType requestedVisibleTypes: Int,
- packageName: String,
- letterboxDetails: Array<LetterboxDetails>
- ) {
- lastSystemBarAttributesParams =
- SystemBarAttributesParams(
- displayId,
- originalAppearance,
- originalAppearanceRegions.toList(),
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- letterboxDetails.toList())
-
- val (appearance, appearanceRegions) =
- modifyAppearanceIfNeeded(
- originalAppearance, originalAppearanceRegions, letterboxDetails)
-
- val barModeChanged = centralSurfaces.setAppearance(appearance)
-
- lightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme)
-
- centralSurfaces.updateBubblesVisibility()
- }
-
- private fun modifyAppearanceIfNeeded(
- appearance: Int,
- appearanceRegions: Array<AppearanceRegion>,
- letterboxDetails: Array<LetterboxDetails>
- ): Pair<Int, Array<AppearanceRegion>> =
- if (shouldUseLetterboxAppearance(letterboxDetails)) {
- val letterboxAppearance =
- letterboxAppearanceCalculator.getLetterboxAppearance(
- appearance, appearanceRegions, letterboxDetails)
- lastLetterboxAppearance = letterboxAppearance
- Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
- } else {
- lastLetterboxAppearance = null
- Pair(appearance, appearanceRegions)
- }
-
- private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
- letterboxDetails.isNotEmpty()
-
- override fun dump(printWriter: PrintWriter, strings: Array<String>) {
- printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
- printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
- }
-}
-
-/**
- * Keeps track of the parameters passed in
- * [SystemBarAttributesListener.onSystemBarAttributesChanged].
- */
-private data class SystemBarAttributesParams(
- val displayId: Int,
- @Appearance val appearance: Int,
- val appearanceRegions: List<AppearanceRegion>,
- val navbarColorManagedByIme: Boolean,
- @Behavior val behavior: Int,
- @InsetsType val requestedVisibleTypes: Int,
- val packageName: String,
- val letterboxes: List<LetterboxDetails>,
-) {
- val letterboxesArray = letterboxes.toTypedArray()
- val appearanceRegionsArray = appearanceRegions.toTypedArray()
- override fun toString(): String {
- val appearanceToString =
- ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
- return """SystemBarAttributesParams(
- displayId=$displayId,
- appearance=$appearanceToString,
- appearanceRegions=$appearanceRegions,
- navbarColorManagedByIme=$navbarColorManagedByIme,
- behavior=$behavior,
- requestedVisibleTypes=$requestedVisibleTypes,
- packageName='$packageName',
- letterboxes=$letterboxes,
- letterboxesArray=${letterboxesArray.contentToString()},
- appearanceRegionsArray=${appearanceRegionsArray.contentToString()}
- )""".trimMargin()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 07c23d1..6ef877b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,22 +26,16 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.IntoSet;
-import dagger.multibindings.Multibinds;
import java.util.Optional;
-import java.util.Set;
import javax.inject.Named;
@@ -159,14 +153,4 @@
static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.heads_up_status_bar_view);
}
-
- /** */
- @Multibinds
- Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
-
- /** */
- @Binds
- @IntoSet
- StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
- SystemBarAttributesListener systemBarAttributesListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 4b1e7a4..b0532ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.time.SystemClock
@@ -57,6 +58,7 @@
class OngoingCallController @Inject constructor(
@Application private val scope: CoroutineScope,
private val context: Context,
+ private val ongoingCallRepository: OngoingCallRepository,
private val notifCollection: CommonNotifCollection,
private val systemClock: SystemClock,
private val activityStarter: ActivityStarter,
@@ -207,7 +209,7 @@
statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(true)
}
updateGestureListening()
- mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+ sendStateChangeEvent()
} else {
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
// will return false and we fall back to typical notification handling.
@@ -261,7 +263,7 @@
tearDownChipView()
statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
- mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+ sendStateChangeEvent()
uidObserver.unregister()
}
@@ -288,6 +290,11 @@
swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
}
+ private fun sendStateChangeEvent() {
+ ongoingCallRepository.setHasOngoingCall(hasOngoingCall())
+ mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+ }
+
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
@@ -374,9 +381,7 @@
if (oldIsCallAppVisible != isCallAppVisible) {
// Animations may be run as a result of the call's state change, so ensure
// the listener is notified on the main thread.
- mainExecutor.execute {
- mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
- }
+ mainExecutor.execute { sendStateChangeEvent() }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
new file mode 100644
index 0000000..da9c45a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Repository storing whether there's current an ongoing call notification.
+ *
+ * This class is used to break a dependency cycle between
+ * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
+ * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two
+ * classes both refer to this repository.
+ */
+@SysUISingleton
+class OngoingCallRepository @Inject constructor() {
+ private val _hasOngoingCall = MutableStateFlow(false)
+ /** True if there's currently an ongoing call notification and false otherwise. */
+ val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow()
+
+ /**
+ * Sets whether there's currently an ongoing call notification. Should only be set from
+ * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
+ */
+ fun setHasOngoingCall(hasOngoingCall: Boolean) {
+ _hasOngoingCall.value = hasOngoingCall
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index efa092b..250fe53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -22,11 +22,12 @@
import androidx.annotation.VisibleForTesting
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -90,50 +91,56 @@
)}"
)
)
- is WifiNetworkModel.Active -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
- val contentDescription =
- ContentDescription.Loaded(
- if (model.isValidated) {
- (levelDesc)
- } else {
- "$levelDesc,${context.getString(NO_INTERNET)}"
- }
- )
- Visible(model.toIcon(showHotspotInfo), contentDescription)
- }
+ is WifiNetworkModel.Active -> model.toIcon(showHotspotInfo, context)
}
- @DrawableRes
- private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
- return if (!showHotspotInfo) {
- this.toBasicIcon()
+ private fun WifiNetworkModel.Active.toIcon(
+ showHotspotInfo: Boolean,
+ context: Context,
+ ): Visible {
+ return if (
+ !showHotspotInfo ||
+ this.hotspotDeviceType == WifiNetworkModel.HotspotDeviceType.NONE
+ ) {
+ this.toBasicIcon(context)
} else {
- when (this.hotspotDeviceType) {
- WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
- WifiNetworkModel.HotspotDeviceType.TABLET ->
- com.android.settingslib.R.drawable.ic_hotspot_tablet
- WifiNetworkModel.HotspotDeviceType.LAPTOP ->
- com.android.settingslib.R.drawable.ic_hotspot_laptop
- WifiNetworkModel.HotspotDeviceType.WATCH ->
- com.android.settingslib.R.drawable.ic_hotspot_watch
- WifiNetworkModel.HotspotDeviceType.AUTO ->
- com.android.settingslib.R.drawable.ic_hotspot_auto
- // Use phone as the default drawable
- WifiNetworkModel.HotspotDeviceType.PHONE,
- WifiNetworkModel.HotspotDeviceType.UNKNOWN,
- WifiNetworkModel.HotspotDeviceType.INVALID ->
- com.android.settingslib.R.drawable.ic_hotspot_phone
- }
+ val icon =
+ when (this.hotspotDeviceType) {
+ WifiNetworkModel.HotspotDeviceType.TABLET ->
+ com.android.settingslib.R.drawable.ic_hotspot_tablet
+ WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+ com.android.settingslib.R.drawable.ic_hotspot_laptop
+ WifiNetworkModel.HotspotDeviceType.WATCH ->
+ com.android.settingslib.R.drawable.ic_hotspot_watch
+ WifiNetworkModel.HotspotDeviceType.AUTO ->
+ com.android.settingslib.R.drawable.ic_hotspot_auto
+ // Use phone as the default drawable
+ WifiNetworkModel.HotspotDeviceType.PHONE,
+ WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+ WifiNetworkModel.HotspotDeviceType.INVALID ->
+ com.android.settingslib.R.drawable.ic_hotspot_phone
+ WifiNetworkModel.HotspotDeviceType.NONE ->
+ throw IllegalStateException("NONE checked earlier")
+ }
+ Visible(
+ icon,
+ ContentDescription.Loaded(context.getString(WIFI_OTHER_DEVICE_CONNECTION)),
+ )
}
}
- @DrawableRes
- private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+ private fun WifiNetworkModel.Active.toBasicIcon(context: Context): Visible {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
return if (this.isValidated) {
- WifiIcons.WIFI_FULL_ICONS[this.level]
+ Visible(
+ WifiIcons.WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc),
+ )
} else {
- WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+ Visible(
+ WifiIcons.WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded("$levelDesc,${context.getString(NO_INTERNET)}"),
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ae8128d..27f8121 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -67,7 +67,7 @@
private final ToastLogger mToastLogger;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
- private ToastOutAnimatorListener mToastOutAnimatorListener;
+ @VisibleForTesting ToastOutAnimatorListener mToastOutAnimatorListener;
@VisibleForTesting SystemUIToast mToast;
private int mOrientation = ORIENTATION_PORTRAIT;
@@ -172,7 +172,7 @@
if (mToast.getOutAnimation() != null) {
Animator animator = mToast.getOutAnimation();
mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
- runnable);
+ runnable, animator);
animator.addListener(mToastOutAnimatorListener);
animator.start();
} else {
@@ -211,14 +211,17 @@
final ToastPresenter mPrevPresenter;
final ITransientNotificationCallback mPrevCallback;
@Nullable Runnable mShowNextToastRunnable;
+ @NonNull private final Animator mAnimator;
ToastOutAnimatorListener(
@NonNull ToastPresenter presenter,
@NonNull ITransientNotificationCallback callback,
- @Nullable Runnable runnable) {
+ @Nullable Runnable runnable,
+ @NonNull Animator animator) {
mPrevPresenter = presenter;
mPrevCallback = callback;
mShowNextToastRunnable = runnable;
+ mAnimator = animator;
}
void setShowNextToastRunnable(Runnable runnable) {
@@ -231,6 +234,8 @@
if (mShowNextToastRunnable != null) {
mShowNextToastRunnable.run();
}
+ mAnimator.removeListener(this);
+ mShowNextToastRunnable = null;
mToastOutAnimatorListener = null;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f6649bd..d54843d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,10 +36,8 @@
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -47,6 +45,7 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -54,6 +53,7 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -157,7 +157,7 @@
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private lateinit var authenticationInteractor: AuthenticationInteractor
+ private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
@@ -229,10 +229,10 @@
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
- authenticationInteractor =
- sceneTestUtils.authenticationInteractor(
- repository = sceneTestUtils.authenticationRepository(),
- sceneInteractor = sceneInteractor
+ deviceEntryInteractor =
+ sceneTestUtils.deviceEntryInteractor(
+ authenticationInteractor = sceneTestUtils.authenticationInteractor(),
+ sceneInteractor = sceneInteractor,
)
underTest =
@@ -268,7 +268,7 @@
keyguardTransitionInteractor,
primaryBouncerInteractor,
) {
- authenticationInteractor
+ deviceEntryInteractor
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 187f098..b8d2bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -88,9 +88,9 @@
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.util.leak.ReferenceTestUtils;
import com.android.systemui.util.settings.SecureSettings;
@@ -121,6 +121,9 @@
public class WindowMagnificationControllerTest extends SysuiTestCase {
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+ // The duration couldn't too short, otherwise the animation check on bounce effect
+ // won't work in expectation. (b/299537784)
+ private static final int BOUNCE_EFFECT_DURATION_MS = 2000;
private static final long ANIMATION_DURATION_MS = 300;
private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
@Mock
@@ -205,6 +208,7 @@
mSysUiState,
() -> mWindowSessionSpy,
mSecureSettings);
+ mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index d3a2a73..0283382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -74,7 +74,6 @@
getSecurityMode = getSecurityMode,
backgroundDispatcher = testUtils.testDispatcher,
userRepository = userRepository,
- keyguardRepository = testUtils.keyguardRepository,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 874053a..a102890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -20,15 +20,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -47,13 +44,7 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val repository: AuthenticationRepository = utils.authenticationRepository()
- private val sceneInteractor = utils.sceneInteractor()
- private val underTest =
- utils.authenticationInteractor(
- repository = repository,
- sceneInteractor = sceneInteractor,
- )
+ private val underTest = utils.authenticationInteractor()
@Test
fun authenticationMethod() =
@@ -79,10 +70,10 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
+ utils.authenticationRepository.setAuthenticationMethod(
+ DataLayerAuthenticationMethodModel.None
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
assertThat(underTest.getAuthenticationMethod())
@@ -95,10 +86,10 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(false)
- }
+ utils.authenticationRepository.setAuthenticationMethod(
+ DataLayerAuthenticationMethodModel.None
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(false)
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
assertThat(underTest.getAuthenticationMethod())
@@ -106,130 +97,6 @@
}
@Test
- fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(false)
- // Toggle isUnlocked, twice.
- //
- // This is done because the underTest.isUnlocked flow doesn't receive values from
- // just changing the state above; the actual isUnlocked state needs to change to
- // cause the logic under test to "pick up" the current state again.
- //
- // It is done twice to make sure that we don't actually change the isUnlocked state
- // from what it originally was.
- setUnlocked(!utils.authenticationRepository.isUnlocked.value)
- runCurrent()
- setUnlocked(!utils.authenticationRepository.isUnlocked.value)
- runCurrent()
- }
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
- fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
-
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
- fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isTrue()
- }
-
- @Test
- fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isFalse()
- }
-
- @Test
- fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
- switchToScene(SceneKey.Gone)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_lockedAndSecured_true() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(false)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isTrue()
- }
-
- @Test
- fun isAuthenticationRequired_lockedAndNotSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(false)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_unlockedAndSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(true)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_unlockedAndNotSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(true)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
fun authenticate_withCorrectPin_returnsTrue() =
testScope.runTest {
val isThrottled by collectLastValue(underTest.isThrottled)
@@ -366,7 +233,6 @@
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -378,13 +244,13 @@
)
)
.isEqualTo(AuthenticationResult.FAILED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -396,13 +262,13 @@
)
)
.isEqualTo(AuthenticationResult.FAILED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -414,13 +280,13 @@
)
)
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isTrue()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(false)
@@ -432,26 +298,27 @@
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
DataLayerAuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.SKIPPED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun throttling() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
val throttling by collectLastValue(underTest.throttling)
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(
@@ -462,7 +329,7 @@
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(isUnlocked).isFalse()
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
@@ -605,8 +472,4 @@
assertThat(hintedPinLength).isNull()
}
-
- private fun switchToScene(sceneKey: SceneKey) {
- sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 92c8a39..a9ba36a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -47,13 +47,16 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
private val underTest =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -76,7 +79,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -111,7 +114,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
utils.authenticationRepository.setAutoConfirmEnabled(true)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -148,7 +151,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -180,7 +183,7 @@
AuthenticationMethodModel.Password
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -215,7 +218,7 @@
AuthenticationMethodModel.Pattern
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -268,7 +271,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.showOrUnlockDevice()
@@ -281,8 +284,8 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.authenticationRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
@@ -298,7 +301,7 @@
AuthenticationMethodModel.Password
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
val customMessage = "Hello there!"
underTest.showOrUnlockDevice(customMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f7dde0..b5177e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -37,17 +37,20 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index da2534d6..b75355a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -44,12 +44,15 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
)
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = utils.sceneInteractor(),
)
@@ -223,6 +226,8 @@
DataLayerAuthenticationMethodModel.Pattern
}
)
- setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
+ model !is DomainLayerAuthenticationMethodModel.None
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c1b3354..0926399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -44,13 +44,16 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ )
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -139,7 +142,7 @@
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -207,7 +210,7 @@
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index bf109d9..2e7c9aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -47,13 +47,16 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ )
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -378,7 +381,7 @@
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2576204..255bbe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -47,12 +47,15 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
)
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -81,7 +84,7 @@
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
@@ -103,7 +106,7 @@
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
@@ -358,7 +361,7 @@
private fun TestScope.lockDeviceAndOpenPinBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
index 9260f63..617e310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
@@ -60,7 +60,7 @@
underTest.startMonitoring()
// There are two receivers registered
- verify(context, times(2))
+ verify(context, times(1))
.registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler))
verify(packageManager).registerPackageMonitorCallback(any(), eq(USER.getIdentifier()))
// context will be used to get PackageManager, the test should clear invocations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 5414259..677108c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -18,10 +18,13 @@
import android.app.ActivityOptions
import android.app.PendingIntent
+import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -30,11 +33,13 @@
import com.android.wm.shell.taskview.TaskView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -43,31 +48,31 @@
@TestableLooper.RunWithLooper
class DetailDialogTest : SysuiTestCase() {
- @Mock
- private lateinit var taskView: TaskView
- @Mock
- private lateinit var broadcastSender: BroadcastSender
- @Mock
- private lateinit var controlViewHolder: ControlViewHolder
- @Mock
- private lateinit var pendingIntent: PendingIntent
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
+ @Rule
+ @JvmField
+ val activityRule: ActivityScenarioRule<EmptyTestActivity> =
+ ActivityScenarioRule(EmptyTestActivity::class.java)
+
+ @Mock private lateinit var taskView: TaskView
+ @Mock private lateinit var broadcastSender: BroadcastSender
+ @Mock private lateinit var controlViewHolder: ControlViewHolder
+ @Mock private lateinit var pendingIntent: PendingIntent
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: DetailDialog
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest = createDialog(pendingIntent)
}
@Test
fun testPendingIntentIsUnModified() {
- // GIVEN the dialog is created with a PendingIntent
- val dialog = createDialog(pendingIntent)
-
// WHEN the TaskView is initialized
- dialog.stateCallback.onInitialized()
+ underTest.stateCallback.onInitialized()
// THEN the PendingIntent used to call startActivity is unmodified by systemui
verify(taskView).startActivity(eq(pendingIntent), any(), any(), any())
@@ -75,11 +80,8 @@
@Test
fun testActivityOptionsAllowBal() {
- // GIVEN the dialog is created with a PendingIntent
- val dialog = createDialog(pendingIntent)
-
// WHEN the TaskView is initialized
- dialog.stateCallback.onInitialized()
+ underTest.stateCallback.onInitialized()
val optionsCaptor = argumentCaptor<ActivityOptions>()
@@ -90,17 +92,41 @@
.isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission)
.isTrue()
+ assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
}
- private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
+ @Test
+ fun testDismissRemovesTheTask() {
+ activityRule.scenario.onActivity {
+ underTest = createDialog(pendingIntent, it)
+ underTest.show()
+
+ underTest.dismiss()
+
+ verify(taskView).removeTask()
+ verify(taskView, never()).release()
+ }
+ }
+
+ @Test
+ fun testTaskRemovalReleasesTaskView() {
+ underTest.stateCallback.onTaskRemovalStarted(0)
+
+ verify(taskView).release()
+ }
+
+ private fun createDialog(
+ pendingIntent: PendingIntent,
+ context: Context = mContext,
+ ): DetailDialog {
return DetailDialog(
- mContext,
+ context,
broadcastSender,
taskView,
pendingIntent,
controlViewHolder,
keyguardStateController,
- activityStarter
+ activityStarter,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
new file mode 100644
index 0000000..8e8cbe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -0,0 +1,127 @@
+package com.android.systemui.deviceentry.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+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.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ private val testUtils = SceneTestUtils(this)
+ private val testScope = testUtils.testScope
+ private val userRepository = FakeUserRepository()
+
+ private lateinit var underTest: DeviceEntryRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ userRepository.setUserInfos(USER_INFOS)
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+ underTest =
+ DeviceEntryRepositoryImpl(
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = testUtils.testDispatcher,
+ userRepository = userRepository,
+ lockPatternUtils = lockPatternUtils,
+ keyguardBypassController = keyguardBypassController,
+ keyguardStateController = keyguardStateController,
+ )
+ }
+
+ @Test
+ fun isUnlocked() =
+ testScope.runTest {
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ Mockito.verify(keyguardStateController, Mockito.atLeastOnce())
+ .addCallback(captor.capture())
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(true)
+ captor.value.onUnlockedChanged()
+ runCurrent()
+ assertThat(isUnlocked).isTrue()
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun isInsecureLockscreenEnabled() =
+ testScope.runTest {
+ whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false)
+ whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true)
+
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(underTest.isInsecureLockscreenEnabled()).isTrue()
+
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+ assertThat(underTest.isInsecureLockscreenEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_disabledInController() =
+ testScope.runTest {
+ whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
+ whenever(keyguardBypassController.bypassEnabled).thenAnswer { false }
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInController() =
+ testScope.runTest {
+ whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true }
+ whenever(keyguardBypassController.bypassEnabled).thenAnswer { true }
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(
+ UserInfo(
+ /* id= */ 100,
+ /* name= */ "First user",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ /* id= */ 101,
+ /* name= */ "Second user",
+ /* flags= */ 0,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
new file mode 100644
index 0000000..55582e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -0,0 +1,234 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryInteractorTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val underTest =
+ utils.deviceEntryInteractor(
+ repository = repository,
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.apply {
+ setInsecureLockscreenEnabled(false)
+
+ // Toggle isUnlocked, twice.
+ //
+ // This is done because the underTest.isUnlocked flow doesn't receive values from
+ // just changing the state above; the actual isUnlocked state needs to change to
+ // cause the logic under test to "pick up" the current state again.
+ //
+ // It is done twice to make sure that we don't actually change the isUnlocked state
+ // from what it originally was.
+ setUnlocked(!isUnlocked.value)
+ runCurrent()
+ setUnlocked(!isUnlocked.value)
+ runCurrent()
+ }
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onLockscreenWithSwipe_isFalse() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Shade)
+
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+
+ assertThat(isDeviceEntered).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+ runCurrent()
+ switchToScene(SceneKey.Shade)
+
+ assertThat(isDeviceEntered).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onBouncer_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Bouncer)
+
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isTrue()
+ }
+
+ @Test
+ fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isFalse()
+ }
+
+ @Test
+ fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndSecured_true() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+
+ assertThat(underTest.isAuthenticationRequired()).isTrue()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndNotSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndNotSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInRepository_true() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setBypassEnabled(true)
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ @Test
+ fun isBypassEnabled_disabledInRepository_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setBypassEnabled(false)
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ private fun switchToScene(sceneKey: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index add601c..8d12e49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -158,8 +158,8 @@
var progress = 0.5f
sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
- // GIVEN a progress event due to a touch on the slider track at threshold
- progress += config.jumpThreshold
+ // GIVEN a progress event due to a touch on the slider track beyond threshold
+ progress += (config.jumpThreshold + 0.01f)
sliderEventProducer.sendEvent(
SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, progress)
)
@@ -191,7 +191,6 @@
// THEN the tracker moves to the jump-track location selected state
assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED)
assertThat(mSeekableSliderTracker.isWaiting).isFalse()
- verify(sliderStateListener).onUpperBookend()
verifyNoMoreInteractions(sliderStateListener)
}
@@ -214,7 +213,6 @@
// THEN the tracker moves to the JUMP_TRACK_LOCATION_SELECTED state
assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED)
assertThat(mSeekableSliderTracker.isWaiting).isFalse()
- verify(sliderStateListener).onLowerBookend()
verifyNoMoreInteractions(sliderStateListener)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 9a2936e..7a13a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -102,7 +102,7 @@
notificationShadeWindowController, powerManager, wallpaperManager
)
keyguardUnlockAnimationController.setLauncherUnlockController(
- launcherUnlockAnimationController)
+ "", launcherUnlockAnimationController)
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9d96ac7..291dda20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -104,6 +104,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeWindowLogger;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -185,6 +186,7 @@
private @Mock SysuiColorExtractor mColorExtractor;
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+ private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
@@ -249,6 +251,7 @@
mScreenOffAnimationController,
mAuthController,
mShadeExpansionStateManager,
+ () -> mShadeInteractor,
mShadeWindowLogger);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 2691860..f93051c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -44,7 +44,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -81,7 +80,6 @@
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
@@ -103,7 +101,6 @@
screenLifecycle,
biometricUnlockController,
keyguardStateController,
- keyguardBypassController,
keyguardUpdateMonitor,
dozeTransitionListener,
dozeParameters,
@@ -213,23 +210,9 @@
}
@Test
- fun isBypassEnabled_disabledInController() {
- whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
- whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
- assertThat(underTest.isBypassEnabled()).isFalse()
- }
-
- @Test
- fun isBypassEnabled_enabledInController() {
- whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
- whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
- assertThat(underTest.isBypassEnabled()).isTrue()
- }
-
- @Test
fun isAodAvailable() = runTest {
val flow = underTest.isAodAvailable
- var isAodAvailable = collectLastValue(flow)
+ val isAodAvailable = collectLastValue(flow)
runCurrent()
val callback =
@@ -273,29 +256,6 @@
}
@Test
- fun isKeyguardUnlocked() =
- testScope.runTest {
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
-
- runCurrent()
- assertThat(isKeyguardUnlocked).isFalse()
-
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
-
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
- captor.value.onUnlockedChanged()
- runCurrent()
- assertThat(isKeyguardUnlocked).isTrue()
-
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- runCurrent()
- assertThat(isKeyguardUnlocked).isFalse()
- }
-
- @Test
fun isDozing() =
testScope.runTest {
underTest.setIsDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 90e217f..82c7fa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -15,8 +15,6 @@
*
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
@@ -27,13 +25,11 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.scene.SceneTestUtils
-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.data.repository.FakeShadeRepository
@@ -42,62 +38,53 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- private lateinit var commandQueue: FakeCommandQueue
- private lateinit var featureFlags: FakeFeatureFlags
- private lateinit var testScope: TestScope
- private lateinit var underTest: KeyguardInteractor
- private lateinit var repository: FakeKeyguardRepository
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var sceneInteractor: SceneInteractor
- private lateinit var transitionState: MutableStateFlow<ObservableTransitionState>
+ private val testUtils = SceneTestUtils(this)
+ private val testScope = testUtils.testScope
+ private val repository = testUtils.keyguardRepository
+ private val sceneInteractor = testUtils.sceneInteractor()
+ private val commandQueue = FakeCommandQueue()
+ private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) }
+ private val bouncerRepository = FakeKeyguardBouncerRepository()
+ private val configurationRepository = FakeConfigurationRepository()
+ private val shadeRepository = FakeShadeRepository()
+ private val transitionState: MutableStateFlow<ObservableTransitionState> =
+ MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
+
+ private val underTest =
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
+ sceneContainerFlags = testUtils.sceneContainerFlags,
+ deviceEntryRepository = testUtils.deviceEntryRepository,
+ bouncerRepository = bouncerRepository,
+ configurationRepository = configurationRepository,
+ shadeRepository = shadeRepository,
+ sceneInteractorProvider = { sceneInteractor },
+ )
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
- featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
- commandQueue = FakeCommandQueue()
- val sceneTestUtils = SceneTestUtils(this)
- testScope = sceneTestUtils.testScope
- repository = sceneTestUtils.keyguardRepository
- bouncerRepository = FakeKeyguardBouncerRepository()
- configurationRepository = FakeConfigurationRepository()
- shadeRepository = FakeShadeRepository()
- sceneInteractor = sceneTestUtils.sceneInteractor()
- transitionState = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
sceneInteractor.setTransitionState(transitionState)
- underTest =
- KeyguardInteractor(
- repository = repository,
- commandQueue = commandQueue,
- featureFlags = featureFlags,
- sceneContainerFlags = sceneTestUtils.sceneContainerFlags,
- bouncerRepository = bouncerRepository,
- configurationRepository = configurationRepository,
- shadeRepository = shadeRepository,
- sceneInteractorProvider = { sceneInteractor },
- )
}
@Test
fun onCameraLaunchDetected() =
testScope.runTest {
val flow = underTest.onCameraLaunchDetected
- var cameraLaunchSource = collectLastValue(flow)
+ val cameraLaunchSource = collectLastValue(flow)
runCurrent()
commandQueue.doForEachCallback {
@@ -175,7 +162,7 @@
@Test
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
- var isVisible = collectLastValue(underTest.isKeyguardVisible)
+ val isVisible = collectLastValue(underTest.isKeyguardVisible)
repository.setKeyguardShowing(true)
repository.setKeyguardOccluded(false)
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 f2636c5..591653e 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
@@ -1226,7 +1226,6 @@
// GIVEN the keyguard is showing locked
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- keyguardRepository.setKeyguardUnlocked(false)
runCurrent()
shadeRepository.setShadeModel(
ShadeModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 1681cfd..6e94691 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -37,10 +37,6 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
private val underTest = createLockscreenSceneViewModel()
@@ -49,8 +45,8 @@
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.authenticationRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ utils.deviceEntryRepository.setUnlocked(true)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -61,7 +57,7 @@
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -88,7 +84,11 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = utils.authenticationInteractor(),
+ sceneInteractor = utils.sceneInteractor(),
+ ),
communalInteractor = utils.communalInteractor(),
longPress =
KeyguardLongPressViewModel(
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 25faeef..de57b60 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
@@ -81,6 +81,7 @@
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock lateinit var logger: MediaViewLogger
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -121,7 +122,8 @@
notifPanelEvents,
settings,
fakeHandler,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ logger,
)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 126dd63..43cf1b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -16,12 +16,15 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.app.PendingIntent
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.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Handler
@@ -49,6 +52,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -67,6 +71,8 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.Arrays
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -74,10 +80,8 @@
class CustomTileTest : SysuiTestCase() {
companion object {
- const val packageName = "test_package"
const val className = "test_class"
- val componentName = ComponentName(packageName, className)
- val TILE_SPEC = CustomTile.toSpec(componentName)
+ val UID = 12345
}
@Mock private lateinit var tileHost: QSHost
@@ -94,11 +98,36 @@
@Mock private lateinit var serviceInfo: ServiceInfo
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@Mock private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var ugm: IUriGrantsManager
private var displayTracker = FakeDisplayTracker(mContext)
private lateinit var customTile: CustomTile
private lateinit var testableLooper: TestableLooper
- private lateinit var customTileBuilder: CustomTile.Builder
+ private val packageName = context.packageName
+ private val componentName = ComponentName(packageName, className)
+ private val TILE_SPEC = CustomTile.toSpec(componentName)
+
+ private val customTileFactory = object : CustomTile.Factory {
+ override fun create(action: String, userContext: Context): CustomTile {
+ return CustomTile(
+ { tileHost },
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ action,
+ userContext,
+ customTileStatePersister,
+ tileServices,
+ displayTracker,
+ ugm,
+ )
+ }
+ }
@Before
fun setUp() {
@@ -116,24 +145,13 @@
`when`(packageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
.thenReturn(serviceInfo)
+ `when`(packageManager.getResourcesForApplication(any<ApplicationInfo>()))
+ .thenReturn(context.resources)
+
serviceInfo.applicationInfo = applicationInfo
- customTileBuilder = CustomTile.Builder(
- { tileHost },
- uiEventLogger,
- testableLooper.looper,
- Handler(testableLooper.looper),
- FalsingManagerFake(),
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- customTileStatePersister,
- tileServices,
- displayTracker
- )
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
}
@@ -146,7 +164,7 @@
`when`(userContext.packageManager).thenReturn(packageManager)
`when`(userContext.userId).thenReturn(10)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, userContext)
tile.initialize()
testableLooper.processAllMessages()
@@ -156,7 +174,7 @@
@Test
fun testToggleableTileHasBooleanState() {
`when`(tileServiceManager.isToggleableTile).thenReturn(true)
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
@@ -173,7 +191,7 @@
@Test
fun testValueUpdatedInBooleanTile() {
`when`(tileServiceManager.isToggleableTile).thenReturn(true)
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
@@ -219,7 +237,7 @@
val t = Tile().apply {
state = Tile.STATE_INACTIVE
}
- customTile.updateTileState(t)
+ customTile.updateTileState(t, UID)
testableLooper.processAllMessages()
verify(customTileStatePersister, never()).persistState(any(), any())
@@ -243,7 +261,7 @@
`when`(tileServiceManager.isActiveTile).thenReturn(true)
`when`(customTileStatePersister
.readState(TileServiceKey(componentName, customTile.user))).thenReturn(t)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
testableLooper.processAllMessages()
@@ -281,11 +299,11 @@
}
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
testableLooper.processAllMessages()
- tile.updateTileState(t)
+ tile.updateTileState(t, UID)
testableLooper.processAllMessages()
@@ -297,13 +315,13 @@
fun testAvailableBeforeInitialization() {
`when`(packageManager.getApplicationInfo(anyString(), anyInt()))
.thenThrow(PackageManager.NameNotFoundException())
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
assertTrue(tile.isAvailable)
}
@Test
fun testNotAvailableAfterInitializationWithoutIcon() {
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
reset(tileHost)
tile.initialize()
testableLooper.processAllMessages()
@@ -315,7 +333,7 @@
fun testInvalidPendingIntentDoesNotStartActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(false)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
assertThrows(IllegalArgumentException::class.java) {
tile.qsTile.activityLaunchForClick = pi
@@ -333,7 +351,7 @@
fun testValidPendingIntentWithNoClickDoesNotStartActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
testableLooper.processAllMessages()
@@ -347,7 +365,7 @@
fun testValidPendingIntentStartsActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
tile.handleClick(mock(LaunchableFrameLayout::class.java))
@@ -363,7 +381,7 @@
fun testActiveTileListensOnceAfterCreated() {
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
tile.postStale()
testableLooper.processAllMessages()
@@ -376,7 +394,7 @@
fun testActiveTileDoesntListenAfterFirstTime() {
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
// Make sure we have an icon in the tile because we don't have a default icon
// This should not be overridden by the retrieved tile that has null icon.
@@ -424,19 +442,128 @@
// Set the tile to listening and apply the tile (unmodified)
customTile.handleSetListening(true)
testableLooper.processAllMessages()
- customTile.updateTileState(tile)
+ customTile.updateTileState(tile, UID)
customTile.refreshState()
testableLooper.processAllMessages()
assertThat(customTile.state.label).isEqualTo(label2)
}
- private fun copyTileUsingParcel(t: Tile): Tile {
- val parcel = Parcel.obtain()
- parcel.setDataPosition(0)
- t.writeToParcel(parcel, 0)
- parcel.setDataPosition(0)
+ @Test
+ fun uriIconLoadSuccess_correctIcon() {
+ val size = 100
+ val icon = mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(drawable)
- return Tile.CREATOR.createFromParcel(parcel)
+ serviceInfo.icon = R.drawable.android
+
+ customTile.handleSetListening(true)
+ testableLooper.processAllMessages()
+ customTile.handleSetListening(false)
+ testableLooper.processAllMessages()
+
+ val tile = copyTileUsingParcel(customTile.qsTile)
+ tile.icon = icon
+
+ customTile.updateTileState(tile, UID)
+
+ customTile.refreshState()
+ testableLooper.processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+ assertThat(
+ areDrawablesEqual(
+ customTile.state.iconSupplier.get().getDrawable(context),
+ drawable,
+ size
+ )
+ ).isTrue()
}
+
+ @Test
+ fun uriIconLoadFailsWithoutGrant_defaultIcon() {
+ val size = 100
+ val drawable = context.getDrawable(R.drawable.cloud)!!
+ val icon = mock(Icon::class.java)
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(null)
+
+ // Give it an icon to prevent issues
+ serviceInfo.icon = R.drawable.android
+
+ customTile.handleSetListening(true)
+ testableLooper.processAllMessages()
+ customTile.handleSetListening(false)
+ testableLooper.processAllMessages()
+
+ val tile = copyTileUsingParcel(customTile.qsTile)
+ tile.icon = icon
+
+ customTile.updateTileState(tile, UID)
+
+ customTile.refreshState()
+ testableLooper.processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+ assertThat(
+ areDrawablesEqual(
+ customTile.state.iconSupplier.get().getDrawable(context),
+ context.getDrawable(R.drawable.android)!!,
+ size
+ )
+ ).isTrue()
+ }
+}
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+ val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+ val canvas1 = Canvas(bm1)
+ val canvas2 = Canvas(bm2)
+
+ drawable1.setBounds(0, 0, size, size)
+ drawable2.setBounds(0, 0, size, size)
+
+ drawable1.draw(canvas1)
+ drawable2.draw(canvas2)
+
+ return equalBitmaps(bm1, bm2).also {
+ bm1.recycle()
+ bm2.recycle()
+ }
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+ if (a.width != b.width || a.height != b.height) return false
+ val w = a.width
+ val h = a.height
+ val aPix = IntArray(w * h)
+ val bPix = IntArray(w * h)
+ a.getPixels(aPix, 0, w, 0, 0, w, h)
+ b.getPixels(bPix, 0, w, 0, 0, w, h)
+ return Arrays.equals(aPix, bPix)
+}
+
+private fun copyTileUsingParcel(t: Tile): Tile {
+ val parcel = Parcel.obtain()
+ parcel.setDataPosition(0)
+ t.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ return Tile.CREATOR.createFromParcel(parcel)
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 365e8a5..78c2acf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -16,6 +16,11 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -26,11 +31,21 @@
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Arrays
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -40,12 +55,19 @@
companion object {
private const val APP_NAME = "App name"
private const val LABEL = "Label"
+ private const val PACKAGE = "package"
+ private const val UID = 12345
+ private val DEFAULT_ICON = R.drawable.android
}
private lateinit var dialog: TileRequestDialog
+ @Mock
+ private lateinit var ugm: IUriGrantsManager
+
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
// Create in looper so we can make sure that the tile is fully updated
TestableLooper.get(this).runWithLooper {
dialog = TileRequestDialog(mContext)
@@ -62,9 +84,9 @@
@Test
fun setTileData_hasCorrectViews() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -77,9 +99,9 @@
@Test
fun setTileData_hasCorrectAppName() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -90,9 +112,9 @@
@Test
fun setTileData_hasCorrectLabel() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -105,9 +127,9 @@
@Test
fun setTileData_hasIcon() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -119,9 +141,9 @@
@Test
fun setTileData_nullIcon_hasIcon() {
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, null)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -134,9 +156,9 @@
@Test
fun setTileData_hasNoStateDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -150,9 +172,9 @@
@Test
fun setTileData_tileNotClickable() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -167,9 +189,9 @@
@Test
fun setTileData_tileHasCorrectContentDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -179,4 +201,111 @@
assertThat(tile.contentDescription).isEqualTo(LABEL)
}
+
+ @Test
+ fun uriIconLoadSuccess_correctIcon() {
+ val tintColor = Color.BLACK
+ val icon = Mockito.mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+ setTint(tintColor)
+ }
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(drawable)
+
+ val size = 100
+
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+ dialog.setTileData(tileData, ugm)
+ dialog.show()
+
+ TestableLooper.get(this).processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+ val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+ val tile = content.getChildAt(1) as QSTileView
+
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+ setTint(tintColor)
+ }
+
+ assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
+ }
+
+ @Test
+ fun uriIconLoadFail_defaultIcon() {
+ val tintColor = Color.BLACK
+ val icon = Mockito.mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+ setTint(tintColor)
+ }
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(null)
+
+ val size = 100
+
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+ dialog.setTileData(tileData, ugm)
+ dialog.show()
+
+ TestableLooper.get(this).processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+ val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+ val tile = content.getChildAt(1) as QSTileView
+
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+ setTint(tintColor)
+ }
+
+ val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
+ setTint(tintColor)
+ }
+
+ assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
+ }
}
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+ val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+ val canvas1 = Canvas(bm1)
+ val canvas2 = Canvas(bm2)
+
+ drawable1.setBounds(0, 0, size, size)
+ drawable2.setBounds(0, 0, size, size)
+
+ drawable1.draw(canvas1)
+ drawable2.draw(canvas2)
+
+ return equalBitmaps(bm1, bm2).also {
+ bm1.recycle()
+ bm2.recycle()
+ }
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+ if (a.width != b.width || a.height != b.height) return false
+ val w = a.width
+ val h = a.height
+ val aPix = IntArray(w * h)
+ val bPix = IntArray(w * h)
+ a.getPixels(aPix, 0, w, 0, 0, w, h)
+ b.getPixels(bPix, 0, w, 0, 0, w, h)
+ return Arrays.equals(aPix, bPix)
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index ccfb5cf..3afa6ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.app.StatusBarManager
import android.content.ComponentName
import android.content.DialogInterface
@@ -57,6 +58,7 @@
private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
private const val TEST_APP_NAME = "App"
private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
}
@Mock
@@ -71,6 +73,8 @@
private lateinit var logger: TileRequestDialogEventLogger
@Mock
private lateinit var icon: Icon
+ @Mock
+ private lateinit var ugm: IUriGrantsManager
private val instanceIdSequence = InstanceIdSequenceFake(1_000)
private lateinit var controller: TileServiceRequestController
@@ -88,7 +92,8 @@
qsHost,
commandQueue,
commandRegistry,
- logger
+ logger,
+ ugm,
) {
tileRequestDialog
}
@@ -98,10 +103,24 @@
@Test
fun requestTileAdd_dataIsPassedToDialog() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+ TileRequestDialog.TileData(
+ TEST_UID,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ TEST_COMPONENT.packageName,
+ ),
+ ugm,
)
}
@@ -110,7 +129,14 @@
`when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
@@ -120,7 +146,7 @@
fun tileAlreadyAdded_logged() {
`when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
verify(logger, never()).logDialogShown(anyString(), any())
@@ -129,19 +155,33 @@
@Test
fun showAllUsers_set() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setShowForAllUsers(true)
}
@Test
fun cancelOnTouchOutside_set() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setCanceledOnTouchOutside(true)
}
@Test
fun dialogShown_logged() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
}
@@ -152,7 +192,14 @@
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -165,7 +212,7 @@
val cancelListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -185,7 +232,14 @@
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
@@ -199,7 +253,7 @@
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
@@ -219,7 +273,14 @@
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
@@ -233,7 +294,7 @@
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
@@ -257,10 +318,24 @@
val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ captor.value.requestAddTile(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+ TileRequestDialog.TileData(
+ TEST_UID,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ TEST_COMPONENT.packageName,
+ ),
+ ugm,
)
}
@@ -271,7 +346,7 @@
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
val c = Callback()
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
+ captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
}
@@ -288,7 +363,14 @@
throw RemoteException()
}
}
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ captor.value.requestAddTile(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -300,7 +382,14 @@
ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
dismissListenerCaptor.value.onDismiss(tileRequestDialog)
@@ -317,7 +406,14 @@
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -338,7 +434,14 @@
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index e222542..067218a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -51,16 +51,17 @@
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.util.leak.GarbageMonitor
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+import org.mockito.Mockito.`when` as whenever
private val specMap = mapOf(
"internet" to InternetTile::class.java,
@@ -98,7 +99,7 @@
class QSFactoryImplTest : SysuiTestCase() {
@Mock private lateinit var qsHost: QSHost
- @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+ @Mock private lateinit var customTileFactory: CustomTile.Factory
@Mock private lateinit var customTile: CustomTile
@Mock private lateinit var internetTile: InternetTile
@@ -139,7 +140,7 @@
whenever(qsHost.context).thenReturn(mContext)
whenever(qsHost.userContext).thenReturn(mContext)
- whenever(customTileBuilder.build()).thenReturn(customTile)
+ whenever(customTileFactory.create(anyString(), any())).thenReturn(customTile)
val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>(
"internet" to Provider { internetTile },
@@ -174,7 +175,7 @@
factory = QSFactoryImpl(
{ qsHost },
- { customTileBuilder },
+ { customTileFactory },
tileMap,
)
// When adding/removing tiles, fix also [specMap] and [tileMap]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
deleted file mode 100644
index 077c813..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.android.systemui.qs.tiles.base
-
-import android.content.Intent
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
-
- @Mock private lateinit var activityStarted: ActivityStarter
-
- lateinit var underTest: QSTileIntentUserActionHandler
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- underTest = QSTileIntentUserActionHandler(activityStarted)
- }
-
- @Test
- fun testPassesIntentToStarter() {
- val intent = Intent("test.ACTION")
-
- underTest.handle(null, intent)
-
- verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
new file mode 100644
index 0000000..06b7a9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.actions
+
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityStarted: ActivityStarter
+
+ lateinit var underTest: QSTileIntentUserActionHandler
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = QSTileIntentUserActionHandler(activityStarted)
+ }
+
+ @Test
+ fun testPassesIntentToStarter() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(null, intent)
+
+ verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
new file mode 100644
index 0000000..4f25d12
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+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.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class DisabledByPolicyInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var restrictedLockProxy: RestrictedLockProxy
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var context: Context
+
+ @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ lateinit var underTest: DisabledByPolicyInteractor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ DisabledByPolicyInteractorImpl(
+ context,
+ activityStarter,
+ restrictedLockProxy,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun testEnabledWhenNoAdmin() =
+ testScope.runTest {
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(null)
+
+ assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+ .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+ }
+
+ @Test
+ fun testDisabledWhenAdminWithNoRestrictions() =
+ testScope.runTest {
+ val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin)
+ whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+ .thenReturn(false)
+
+ val result =
+ underTest.isDisabled(TEST_USER, TEST_RESTRICTION)
+ as DisabledByPolicyInteractor.PolicyResult.TileDisabled
+ assertThat(result.admin).isEqualTo(admin)
+ }
+
+ @Test
+ fun testEnabledWhenAdminWithRestrictions() =
+ testScope.runTest {
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(ADMIN)
+ whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+ .thenReturn(true)
+
+ assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+ .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+ }
+
+ @Test
+ fun testHandleDisabledByPolicy() {
+ val result =
+ underTest.handlePolicyResult(
+ DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
+ )
+
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ assertThat(result).isTrue()
+ verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+ assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
+ }
+
+ @Test
+ fun testHandleEnabled() {
+ val result =
+ underTest.handlePolicyResult(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+
+ assertThat(result).isFalse()
+ verify(activityStarter, never())
+ .postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+ }
+
+ private companion object {
+ const val TEST_USER = 1
+ const val TEST_RESTRICTION = "test_restriction"
+
+ val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls")
+
+ val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index eacb080..9024c6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,8 +1,8 @@
package com.android.systemui.qs.tiles.viewmodel
import android.graphics.drawable.ShapeDrawable
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.internal.logging.InstanceId
import com.android.systemui.RoboPilotTest
@@ -10,6 +10,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
@@ -29,12 +30,13 @@
// TODO(b/299909368): Add more tests
@MediumTest
@RoboPilotTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
+ private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
private val testCoroutineDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testCoroutineDispatcher)
@@ -68,18 +70,18 @@
scope: TestScope,
config: QSTileConfig = TEST_QS_TILE_CONFIG,
): QSTileViewModel =
- object :
- BaseQSTileViewModel<Any>(
- config,
- fakeQSTileUserActionInteractor,
- fakeQSTileDataInteractor,
- object : QSTileDataToStateMapper<Any> {
- override fun map(config: QSTileConfig, data: Any): QSTileState =
- QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
- },
- testCoroutineDispatcher,
- tileScope = scope.backgroundScope,
- ) {}
+ BaseQSTileViewModel(
+ config,
+ fakeQSTileUserActionInteractor,
+ fakeQSTileDataInteractor,
+ object : QSTileDataToStateMapper<Any> {
+ override fun map(config: QSTileConfig, data: Any): QSTileState =
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+ },
+ fakeDisabledByPolicyInteractor,
+ testCoroutineDispatcher,
+ scope.backgroundScope,
+ )
private companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 8ae8930..f1c99d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -48,11 +48,6 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
-
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private var mobileIconsViewModel: MobileIconsViewModel =
@@ -85,10 +80,17 @@
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val authenticationInteractor = utils.authenticationInteractor()
+
underTest =
QuickSettingsSceneViewModel(
bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
@@ -101,7 +103,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -114,7 +116,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 85bd92b..5259013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -83,7 +83,6 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
-
private val sceneContainerConfig = utils.fakeSceneContainerConfig()
private val sceneRepository =
utils.fakeSceneContainerRepository(
@@ -93,14 +92,12 @@
utils.sceneInteractor(
repository = sceneRepository,
)
-
- private val authenticationRepository = utils.authenticationRepository()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
-
private val communalInteractor = utils.communalInteractor()
private val transitionState =
@@ -116,6 +113,7 @@
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -128,7 +126,7 @@
private val lockscreenSceneViewModel =
LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
longPress =
KeyguardLongPressViewModel(
@@ -178,12 +176,12 @@
shadeSceneViewModel =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
bouncerInteractor = bouncerInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
)
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker)
@@ -191,6 +189,7 @@
SceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
keyguardInteractor = keyguardInteractor,
flags = utils.sceneContainerFlags,
@@ -417,13 +416,13 @@
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
// is not an observable that can trigger a new evaluation.
- authenticationRepository.setLockscreenEnabled(
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
authMethod !is DomainLayerAuthenticationMethodModel.None
)
- authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+ utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
if (!authMethod.isSecure) {
// When the auth method is not secure, the device is never considered locked.
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
}
runCurrent()
}
@@ -528,14 +527,14 @@
.that(authMethod.isSecure)
.isTrue()
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
}
/** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
private fun TestScope.unlockDevice() {
assertWithMessage("Cannot unlock a device that's already unlocked!")
- .that(authenticationInteractor.isUnlocked.value)
+ .that(deviceEntryInteractor.isUnlocked.value)
.isFalse()
emulateUserDrivenTransition(SceneKey.Bouncer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16fdf8e..00a20cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -59,17 +59,13 @@
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
private val sceneContainerFlags = utils.sceneContainerFlags
- private val authenticationRepository = utils.authenticationRepository()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
- private val keyguardRepository = utils.keyguardRepository
- private val keyguardInteractor =
- utils.keyguardInteractor(
- repository = keyguardRepository,
- )
+ private val keyguardInteractor = utils.keyguardInteractor()
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
@@ -77,6 +73,7 @@
SceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
keyguardInteractor = keyguardInteractor,
flags = sceneContainerFlags,
@@ -141,7 +138,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -157,7 +154,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -173,7 +170,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -189,7 +186,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -205,7 +202,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -251,7 +248,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -267,7 +264,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -283,7 +280,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -300,9 +297,9 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -389,11 +386,11 @@
runCurrent()
verify(falsingCollector).setShowingAod(false)
- keyguardRepository.setIsDozing(true)
+ utils.keyguardRepository.setIsDozing(true)
runCurrent()
verify(falsingCollector).setShowingAod(true)
- keyguardRepository.setIsDozing(false)
+ utils.keyguardRepository.setIsDozing(false)
runCurrent()
verify(falsingCollector, times(2)).setShowingAod(false)
}
@@ -401,7 +398,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
testScope.runTest {
- keyguardRepository.setAodAvailable(false)
+ utils.keyguardRepository.setAodAvailable(false)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -414,31 +411,31 @@
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, times(1)).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
verify(falsingCollector, times(1)).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
verify(falsingCollector, times(2)).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(2)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
@@ -448,7 +445,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
testScope.runTest {
- keyguardRepository.setAodAvailable(true)
+ utils.keyguardRepository.setAodAvailable(true)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -461,31 +458,31 @@
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
@@ -521,8 +518,8 @@
): MutableStateFlow<ObservableTransitionState> {
assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
sceneContainerFlags.enabled = true
- authenticationRepository.setUnlocked(isDeviceUnlocked)
- keyguardRepository.setBypassEnabled(isBypassEnabled)
+ utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
+ utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -534,8 +531,10 @@
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
- authenticationRepository.setLockscreenEnabled(
+ utils.authenticationRepository.setAuthenticationMethod(
+ authenticationMethod.toDataLayer()
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
authenticationMethod != AuthenticationMethodModel.None
)
}
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 2d00e8c..2ed2090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -46,22 +47,34 @@
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
import com.google.common.util.concurrent.MoreExecutors;
@@ -77,8 +90,10 @@
import java.util.List;
import java.util.concurrent.Executor;
+import kotlinx.coroutines.test.TestScope;
+
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@@ -102,6 +117,9 @@
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
+ private SceneTestUtils mUtils = new SceneTestUtils(this);
+ private TestScope mTestScope = mUtils.getTestScope();
+ private ShadeInteractor mShadeInteractor;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
private float mPreferredRefreshRate = -1;
@@ -119,6 +137,23 @@
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
+ mShadeInteractor =
+ new ShadeInteractor(
+ mTestScope.getBackgroundScope(),
+ new FakeDisableFlagsRepository(),
+ new FakeSceneContainerFlags(),
+ mUtils::sceneInteractor,
+ new FakeKeyguardRepository(),
+ new FakeUserSetupRepository(),
+ mock(DeviceProvisionedController.class),
+ mock(UserInteractor.class),
+ new SharedNotificationContainerInteractor(
+ new FakeConfigurationRepository(),
+ mContext,
+ new ResourcesSplitShadeStateController()),
+ new FakeShadeRepository()
+ );
+
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
@@ -136,6 +171,7 @@
mScreenOffAnimationController,
mAuthController,
mShadeExpansionStateManager,
+ () -> mShadeInteractor,
mShadeWindowLogger) {
@Override
protected boolean isDebuggable() {
@@ -272,9 +308,9 @@
@Test
public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
- mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
+ mNotificationShadeWindowController.onShadeOrQsExpanded(true);
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
+ mNotificationShadeWindowController.onShadeOrQsExpanded(true);
verifyNoMoreInteractions(mWindowManager);
mNotificationShadeWindowController.setNotificationShadeFocusable(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5c75d9c..602bd5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -47,9 +47,10 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -88,9 +89,10 @@
underTest =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
@@ -103,7 +105,7 @@
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -113,7 +115,7 @@
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -122,7 +124,7 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -134,7 +136,7 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -147,7 +149,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -160,7 +162,7 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 39accfb..19863ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,9 +16,18 @@
package com.android.systemui.statusbar;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -32,6 +41,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Handler;
@@ -49,8 +59,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
@@ -66,6 +74,7 @@
import com.google.android.collect.Lists;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -112,7 +121,6 @@
private NotificationEntry mCurrentUserNotif;
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
- private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
@Before
public void setUp() {
@@ -127,8 +135,11 @@
mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
UserManager.USER_TYPE_PROFILE_MANAGED);
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
- mCurrentUser, mSecondaryUser, mWorkUser));
+ mCurrentUser, mWorkUser));
+ when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+ mSecondaryUser));
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
Handler.createAsync(Looper.myLooper()));
@@ -153,14 +164,14 @@
@Test
public void testLockScreenShowNotificationsFalse() {
- mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testLockScreenShowNotificationsTrue() {
- mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@@ -227,6 +238,7 @@
// THEN work profile notification is redacted
assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
}
@Test
@@ -238,6 +250,7 @@
// THEN work profile notification isn't redacted
assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
}
@Test
@@ -295,21 +308,12 @@
@Test
public void testUserSwitchedCallsOnUserSwitching() {
- mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, true);
mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
mContext);
verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
}
@Test
- public void testUserSwitchedCallsOnUserSwitched() {
- mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, false);
- mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id,
- mContext);
- verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
- }
-
- @Test
public void testIsLockscreenPublicMode() {
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
@@ -348,6 +352,262 @@
verify(listener, never()).onNotificationStateChanged();
}
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications() {
+ // User allows them
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides notifs on lockscreen
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Ignore("b/286230167")
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications_userAll() {
+ // User allows them
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(USER_ALL));
+ }
+
+ @Test
+ @Ignore("b/286230167")
+ public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+
+ // TODO (b/286230167): enable assertion
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+
+ // TODO (b/286230167): enable assertion. It's currently called 4 times.
+ //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications_userAll() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+ .setNotification(new Notification())
+ .setUser(UserHandle.ALL)
+ .build()));
+ }
+
+ @Test
+ public void testDevicePolicyPrivateNotifications_secondary() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+
+ // TODO (b/286230167): enable assertion. It's currently called 5 times.
+ //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ }
+
+ @Test
+ public void testHideNotifications_primary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Ignore("b/286230167")
+ @Test
+ public void testHideNotifications_workProfile() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+
+ TestNotificationLockscreenUserManager lockscreenUserManager
+ = new TestNotificationLockscreenUserManager(mContext);
+ lockscreenUserManager.setUpWithPresenter(mPresenter);
+
+ lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testShowNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+
+ TestNotificationLockscreenUserManager lockscreenUserManager
+ = new TestNotificationLockscreenUserManager(mContext);
+ lockscreenUserManager.setUpWithPresenter(mPresenter);
+
+ lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Ignore("b/286230167")
+ @Test
+ public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ // DevicePolicy allows notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(0);
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ // KeyguardManager does not
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+ assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ // DevicePolicy allows notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(0);
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ // KeyguardManager does not
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_settingsChange() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+ // User disables
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Ignore("b/286230167")
+ @Test
+ public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+ // DevicePolicy disables notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+ BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+ when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
private class TestNotificationLockscreenUserManager
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
@@ -368,8 +628,7 @@
mKeyguardStateController,
mSettings,
mock(DumpManager.class),
- mock(LockPatternUtils.class),
- mFakeFeatureFlags);
+ mock(LockPatternUtils.class));
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 2c8900c..61ba464 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -16,11 +16,18 @@
package com.android.systemui.statusbar.data.repository
+import com.android.systemui.statusbar.data.model.StatusBarAppearance
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import kotlinx.coroutines.flow.MutableStateFlow
class FakeStatusBarModeRepository : StatusBarModeRepository {
override val isTransientShown = MutableStateFlow(false)
override val isInFullscreenMode = MutableStateFlow(false)
+ override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
+ override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
+
+ override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
override fun showTransient() {
isTransientShown.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 27c1ba3..d1a46fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -16,20 +16,36 @@
package com.android.systemui.statusbar.data.repository
+import android.graphics.Rect
import android.view.WindowInsets
import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.BoundsPair
+import com.android.systemui.statusbar.phone.LetterboxAppearance
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.verify
@@ -37,14 +53,26 @@
class StatusBarModeRepositoryImplTest : SysuiTestCase() {
private val testScope = TestScope()
private val commandQueue = mock<CommandQueue>()
+ private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
+ private val statusBarBoundsProvider = mock<StatusBarBoundsProvider>()
+ private val statusBarFragmentComponent =
+ mock<StatusBarFragmentComponent>().also {
+ whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
+ }
+ private val ongoingCallRepository = OngoingCallRepository()
private val underTest =
StatusBarModeRepositoryImpl(
testScope.backgroundScope,
DISPLAY_ID,
commandQueue,
+ letterboxAppearanceCalculator,
+ ongoingCallRepository,
)
- .apply { this.start() }
+ .apply {
+ this.start()
+ this.onStatusBarViewInitialized(statusBarFragmentComponent)
+ }
private val commandQueueCallback: CommandQueue.Callbacks
get() {
@@ -53,6 +81,15 @@
return callbackCaptor.value
}
+ private val statusBarBoundsChangeListener: StatusBarBoundsProvider.BoundsChangeListener
+ get() {
+ val callbackCaptor = argumentCaptor<StatusBarBoundsProvider.BoundsChangeListener>()
+ verify(statusBarBoundsProvider).addChangeListener(capture(callbackCaptor))
+ return callbackCaptor.value
+ }
+
+ @Before fun setUp() {}
+
@Test
fun isTransientShown_commandQueueShow_wrongDisplayId_notUpdated() {
commandQueueCallback.showTransient(
@@ -228,6 +265,240 @@
assertThat(latest).isTrue()
}
+ @Test
+ fun statusBarAppearance_navBarColorManaged_matchesCallbackValue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(navbarColorManagedByIme = true)
+
+ assertThat(latest!!.navbarColorManagedByIme).isTrue()
+
+ onSystemBarAttributesChanged(navbarColorManagedByIme = false)
+
+ assertThat(latest!!.navbarColorManagedByIme).isFalse()
+ }
+
+ @Test
+ fun statusBarAppearance_appearanceRegions_noLetterboxDetails_usesCallbackValues() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ eq(APPEARANCE),
+ eq(APPEARANCE_REGIONS),
+ eq(LETTERBOX_DETAILS),
+ any(),
+ )
+ )
+ .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
+
+ // WHEN the letterbox details are empty
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE,
+ appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+ letterboxDetails = emptyArray(),
+ )
+
+ // THEN the appearance regions passed to the callback are used, *not*
+ // REGIONS_FROM_LETTERBOX_CALCULATOR
+ assertThat(latest!!.appearanceRegions).isEqualTo(APPEARANCE_REGIONS)
+ assertThat(latest!!.appearanceRegions).isNotEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
+ }
+
+ @Test
+ fun statusBarAppearance_appearanceRegions_letterboxDetails_usesLetterboxCalculator() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ eq(APPEARANCE),
+ eq(APPEARANCE_REGIONS),
+ eq(LETTERBOX_DETAILS),
+ any(),
+ )
+ )
+ .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
+
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE,
+ appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+ letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
+ )
+
+ assertThat(latest!!.appearanceRegions).isEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
+ }
+
+ @Test
+ fun statusBarAppearance_boundsChanged_appearanceReFetched() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ // First, start with some appearances
+ val startingLetterboxAppearance =
+ LetterboxAppearance(
+ APPEARANCE_LIGHT_STATUS_BARS,
+ listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1)))
+ )
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ eq(APPEARANCE),
+ eq(APPEARANCE_REGIONS),
+ eq(LETTERBOX_DETAILS),
+ any(),
+ )
+ )
+ .thenReturn(startingLetterboxAppearance)
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE,
+ appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+ letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
+ )
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
+ assertThat(latest!!.appearanceRegions)
+ .isEqualTo(startingLetterboxAppearance.appearanceRegions)
+
+ // WHEN there's a new appearance and we get new status bar bounds
+ val newLetterboxAppearance =
+ LetterboxAppearance(
+ APPEARANCE_LOW_PROFILE_BARS,
+ listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40)))
+ )
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ eq(APPEARANCE),
+ eq(APPEARANCE_REGIONS),
+ eq(LETTERBOX_DETAILS),
+ any(),
+ )
+ )
+ .thenReturn(newLetterboxAppearance)
+ statusBarBoundsChangeListener.onStatusBarBoundsChanged(
+ BoundsPair(Rect(0, 0, 50, 50), Rect(0, 0, 60, 60))
+ )
+
+ // THEN the new appearances are used
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
+ assertThat(latest!!.appearanceRegions)
+ .isEqualTo(newLetterboxAppearance.appearanceRegions)
+ }
+
+ @Test
+ fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ ongoingCallRepository.setHasOngoingCall(true)
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+ }
+
+ @Test
+ fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ ongoingCallRepository.setHasOngoingCall(true)
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.statusBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
+ fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ ongoingCallRepository.setHasOngoingCall(false)
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
+ fun statusBarMode_transientShown_semiTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ underTest.showTransient()
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+ }
+
+ @Test
+ fun statusBarMode_appearanceLowProfileAndOpaque_lightsOut() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT)
+ }
+
+ @Test
+ fun statusBarMode_appearanceLowProfile_lightsOutTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE_LOW_PROFILE_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
+ }
+
+ @Test
+ fun statusBarMode_appearanceOpaque_opaque() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
+ fun statusBarMode_appearanceSemiTransparent_semiTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(
+ appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+ }
+
+ @Test
+ fun statusBarMode_appearanceNone_transparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ onSystemBarAttributesChanged(
+ appearance = 0,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
+ }
+
private fun onSystemBarAttributesChanged(
displayId: Int = DISPLAY_ID,
@WindowInsetsController.Appearance appearance: Int = APPEARANCE_OPAQUE_STATUS_BARS,
@@ -253,5 +524,21 @@
private companion object {
const val DISPLAY_ID = 5
+ private const val APPEARANCE = APPEARANCE_LIGHT_STATUS_BARS
+ private val APPEARANCE_REGION = AppearanceRegion(APPEARANCE, Rect(0, 0, 150, 300))
+ private val APPEARANCE_REGIONS = listOf(APPEARANCE_REGION)
+ private val LETTERBOX_DETAILS =
+ listOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds= */ Rect(0, 0, 10, 10),
+ /* letterboxFullBounds= */ Rect(0, 0, 20, 20),
+ /* appAppearance= */ 0
+ )
+ )
+ private val REGIONS_FROM_LETTERBOX_CALCULATOR =
+ listOf(AppearanceRegion(APPEARANCE, Rect(0, 0, 10, 20)))
+ private const val LETTERBOXED_APPEARANCE = APPEARANCE_LOW_PROFILE_BARS
+ private val CALCULATOR_LETTERBOX_APPEARANCE =
+ LetterboxAppearance(LETTERBOXED_APPEARANCE, REGIONS_FROM_LETTERBOX_CALCULATOR)
}
}
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 c71c0c57..a5d3484 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
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -35,14 +34,11 @@
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.view.HapticFeedbackConstants;
-import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.internal.statusbar.LetterboxDetails;
-import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
@@ -100,7 +96,6 @@
@Mock private VibratorHelper mVibratorHelper;
@Mock private Vibrator mVibrator;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
- @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private UserTracker mUserTracker;
@Mock private QSHost mQSHost;
@@ -139,7 +134,6 @@
Optional.of(mVibrator),
new DisableFlagsLogger(),
DEFAULT_DISPLAY,
- mSystemBarAttributesListener,
mCameraLauncherLazy,
mUserTracker,
mQSHost,
@@ -197,62 +191,6 @@
}
@Test
- public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() {
- int displayId = DEFAULT_DISPLAY;
- int appearance = 123;
- AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
- boolean navbarColorManagedByIme = true;
- int behavior = 456;
- int requestedVisibleTypes = WindowInsets.Type.systemBars();
- String packageName = "test package name";
- LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
-
- mSbcqCallbacks.onSystemBarAttributesChanged(
- displayId,
- appearance,
- appearanceRegions,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- letterboxDetails);
-
- verify(mSystemBarAttributesListener).onSystemBarAttributesChanged(
- displayId,
- appearance,
- appearanceRegions,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- letterboxDetails
- );
- }
-
- @Test
- public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() {
- int appearance = 123;
- AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
- boolean navbarColorManagedByIme = true;
- int behavior = 456;
- int requestedVisibleTypes = WindowInsets.Type.systemBars();
- String packageName = "test package name";
- LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
-
- mSbcqCallbacks.onSystemBarAttributesChanged(
- DEFAULT_DISPLAY + 1,
- appearance,
- appearanceRegions,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- letterboxDetails);
-
- verifyZeroInteractions(mSystemBarAttributesListener);
- }
-
- @Test
public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 0b171b2..e33fa22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -165,7 +165,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -295,7 +294,6 @@
@Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
@Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
@Mock private WallpaperController mWallpaperController;
- @Mock private OngoingCallController mOngoingCallController;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -535,7 +533,6 @@
mBrightnessSliderFactory,
mScreenOffAnimationController,
mWallpaperController,
- mOngoingCallController,
mStatusBarHideIconsForBouncerManager,
mLockscreenTransitionController,
mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index b2dc0dc..e7b287c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -26,7 +26,6 @@
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,15 +46,12 @@
private val TEST_APPEARANCE_REGION_BOUNDS = Rect(0, 0, 20, 100)
private val TEST_APPEARANCE_REGION =
AppearanceRegion(TEST_APPEARANCE, TEST_APPEARANCE_REGION_BOUNDS)
- private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
+ private val TEST_APPEARANCE_REGIONS = listOf(TEST_APPEARANCE_REGION)
private val TEST_WINDOW_BOUNDS = Rect(0, 0, 500, 500)
}
@get:Rule var expect = Expect.create()
- @Mock private lateinit var lightBarController: LightBarController
- @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
- @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
@@ -64,24 +60,21 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
calculator =
- LetterboxAppearanceCalculator(
- lightBarController, dumpManager, letterboxBackgroundProvider)
- calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+ LetterboxAppearanceCalculator(context, dumpManager, letterboxBackgroundProvider)
whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
}
@Test
fun getLetterboxAppearance_overlapStartSide_returnsOriginalWithScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(50, 50, 150, 150))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
expect
.that(letterboxAppearance.appearance)
@@ -91,13 +84,13 @@
@Test
fun getLetterboxAppearance_overlapEndSide_returnsOriginalWithScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(150, 50, 250, 150))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
expect
.that(letterboxAppearance.appearance)
@@ -114,14 +107,12 @@
val statusBarStartSideBoundsCopy = Rect(statusBarStartSideBounds)
val statusBarEndSideBoundsCopy = Rect(statusBarEndSideBounds)
val letterBoxInnerBoundsCopy = Rect(letterBoxInnerBounds)
- whenever(statusBarBoundsProvider.visibleStartSideBounds)
- .thenReturn(statusBarStartSideBounds)
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(statusBarEndSideBounds)
calculator.getLetterboxAppearance(
TEST_APPEARANCE,
TEST_APPEARANCE_REGIONS,
- arrayOf(letterboxWithInnerBounds(letterBoxInnerBounds))
+ listOf(letterboxWithInnerBounds(letterBoxInnerBounds)),
+ BoundsPair(statusBarStartSideBounds, statusBarEndSideBounds)
)
expect.that(statusBarStartSideBounds).isEqualTo(statusBarStartSideBoundsCopy)
@@ -132,13 +123,13 @@
@Test
fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
expect
.that(letterboxAppearance.appearance)
@@ -148,66 +139,66 @@
@Test
fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
}
@Test
fun getLetterboxAppearance_letterboxContainsStartSide_returnsAppearanceWithoutScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(0, 0, 101, 101))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
}
@Test
fun getLetterboxAppearance_letterboxContainsEndSide_returnsAppearanceWithoutScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(199, 0, 301, 101))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
}
@Test
fun getLetterboxAppearance_letterboxContainsEntireStatusBar_returnsAppearanceWithoutScrim() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val start = Rect(0, 0, 100, 100)
+ val end = Rect(200, 0, 300, 100)
val letterbox = letterboxWithInnerBounds(Rect(0, 0, 300, 100))
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
}
@Test
fun getLetterboxAppearance_returnsAdaptedAppearanceRegions_basedOnLetterboxInnerBounds() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ val start = Rect(0, 0, 0, 0)
+ val end = Rect(0, 0, 0, 0)
val letterbox = letterboxWithInnerBounds(Rect(150, 0, 300, 800))
val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+ TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end))
val letterboxAdaptedRegion = letterboxRegion.copy(bounds = letterbox.letterboxInnerBounds)
assertThat(letterboxAppearance.appearanceRegions.toList()).contains(letterboxAdaptedRegion)
@@ -216,8 +207,8 @@
@Test
fun getLetterboxAppearance_returnsDefaultAppearanceRegions_basedOnLetterboxOuterBounds() {
- whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
- whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ val start = Rect(0, 0, 0, 0)
+ val end = Rect(0, 0, 0, 0)
val letterbox =
letterboxWithBounds(
innerBounds = Rect(left = 25, top = 0, right = 75, bottom = 100),
@@ -226,16 +217,20 @@
val letterboxAppearance =
calculator.getLetterboxAppearance(
- TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+ TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end))
val outerRegions =
listOf(
AppearanceRegion(
- DEFAULT_APPEARANCE, Rect(left = 0, top = 0, right = 25, bottom = 100)),
+ DEFAULT_APPEARANCE,
+ Rect(left = 0, top = 0, right = 25, bottom = 100),
+ ),
AppearanceRegion(
- DEFAULT_APPEARANCE, Rect(left = 75, top = 0, right = 100, bottom = 100)),
+ DEFAULT_APPEARANCE,
+ Rect(left = 75, top = 0, right = 100, bottom = 100),
+ ),
)
- assertThat(letterboxAppearance.appearanceRegions.toList())
+ assertThat(letterboxAppearance.appearanceRegions)
.containsAtLeastElementsIn(outerRegions)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index df3c1e5..c45ecf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,12 +22,14 @@
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -42,11 +44,16 @@
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.view.AppearanceRegion;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.model.StatusBarMode;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import org.junit.Before;
import org.junit.Test;
@@ -54,6 +61,10 @@
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import kotlinx.coroutines.test.TestScope;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -62,10 +73,16 @@
private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
+ private static final BoundsPair STATUS_BAR_BOUNDS = new BoundsPair(
+ /* start= */ new Rect(0, 0, 10, 10),
+ /* end= */ new Rect(0, 0, 20, 20));
private LightBarTransitionsController mLightBarTransitionsController;
private LightBarTransitionsController mNavBarController;
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final FakeStatusBarModeRepository mStatusBarModeRepository =
+ new FakeStatusBarModeRepository();
@Before
public void setup() {
@@ -77,11 +94,14 @@
mLightBarTransitionsController);
mLightBarController = new LightBarController(
mContext,
+ new JavaAdapter(mTestScope),
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
+ mStatusBarModeRepository,
mock(DumpManager.class),
new FakeDisplayTracker(mContext));
+ mLightBarController.start();
}
private static GradientColors makeColors(@ColorInt int bgColor) {
@@ -96,13 +116,19 @@
public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksLight() {
final Rect firstBounds = new Rect(0, 0, 1, 1);
final Rect secondBounds = new Rect(1, 0, 2, 1);
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = Arrays.asList(
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
verify(mStatusBarIconController).setIconsDarkArea(eq(null));
verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
}
@@ -111,13 +137,19 @@
public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightOneStackDark() {
final Rect firstBounds = new Rect(0, 0, 1, 1);
final Rect secondBounds = new Rect(1, 0, 2, 1);
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = Arrays.asList(
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
new AppearanceRegion(0 /* appearance */, secondBounds)
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
assertTrue(captor.getValue().contains(firstBounds));
@@ -128,13 +160,19 @@
public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackDarkOneStackLight() {
final Rect firstBounds = new Rect(0, 0, 1, 1);
final Rect secondBounds = new Rect(1, 0, 2, 1);
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = Arrays.asList(
new AppearanceRegion(0 /* appearance */, firstBounds),
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
assertTrue(captor.getValue().contains(secondBounds));
@@ -146,14 +184,20 @@
final Rect firstBounds = new Rect(0, 0, 1, 1);
final Rect secondBounds = new Rect(1, 0, 2, 1);
final Rect thirdBounds = new Rect(2, 0, 3, 1);
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = Arrays.asList(
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
new AppearanceRegion(0 /* appearance */, secondBounds),
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
assertTrue(captor.getValue().contains(firstBounds));
@@ -165,40 +209,121 @@
public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksDark() {
final Rect firstBounds = new Rect(0, 0, 1, 1);
final Rect secondBounds = new Rect(1, 0, 2, 1);
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = Arrays.asList(
new AppearanceRegion(0 /* appearance */, firstBounds),
new AppearanceRegion(0 /* appearance */, secondBounds)
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean());
}
@Test
public void testOnStatusBarAppearanceChanged_singleStack_light() {
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = List.of(
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
verify(mStatusBarIconController).setIconsDarkArea(eq(null));
verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
}
@Test
public void testOnStatusBarAppearanceChanged_singleStack_dark() {
- final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+ final List<AppearanceRegion> appearanceRegions = List.of(
new AppearanceRegion(0, new Rect(0, 0, 1, 1))
- };
- mLightBarController.onStatusBarAppearanceChanged(
- appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
- false /* navbarColorManagedByIme */);
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean());
}
@Test
+ public void testOnStatusBarAppearanceChanged_statusBarModeChanged_statusRedrawn() {
+ final List<AppearanceRegion> appearanceRegions = List.of(
+ new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
+ );
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+ reset(mStatusBarIconController);
+
+ // WHEN the same appearance regions but different status bar mode is sent
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.LIGHTS_OUT_TRANSPARENT,
+ STATUS_BAR_BOUNDS,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
+ // THEN the StatusBarIconController gets a redraw request
+ verify(mStatusBarIconController).setIconsDarkArea(any());
+ }
+
+ /** Regression test for b/301605450. */
+ @Test
+ public void testOnStatusBarAppearanceChanged_statusBarBoundsChanged_statusRedrawn() {
+ final List<AppearanceRegion> appearanceRegions = List.of(
+ new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
+ );
+ BoundsPair startingBounds = new BoundsPair(
+ /* start= */ new Rect(0, 0, 10, 10),
+ /* end= */ new Rect(0, 0, 20, 20));
+
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ startingBounds,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+ reset(mStatusBarIconController);
+
+ // WHEN the same appearance regions but different status bar bounds are sent
+ BoundsPair newBounds = new BoundsPair(
+ /* start= */ new Rect(0, 0, 30, 30),
+ /* end= */ new Rect(0, 0, 40, 40));
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ new StatusBarAppearance(
+ StatusBarMode.TRANSPARENT,
+ newBounds,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false));
+ mTestScope.getTestScheduler().advanceUntilIdle();
+
+ // THEN the StatusBarIconController gets a redraw request
+ verify(mStatusBarIconController).setIconsDarkArea(any());
+ }
+
+ @Test
public void validateNavBarChangesUpdateIcons() {
// On the launcher in dark mode buttons are light
mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
index d84010d..61da701 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -24,14 +24,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.any
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -60,8 +61,9 @@
startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) }
endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) }
- boundsProvider =
- StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent)
+ boundsProvider = StatusBarBoundsProvider(startSideContent, endSideContent)
+ boundsProvider.addChangeListener(boundsChangeListener)
+ reset(boundsChangeListener)
}
@Test
@@ -81,7 +83,7 @@
startSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener).onStatusBarBoundsChanged()
+ verify(boundsChangeListener).onStatusBarBoundsChanged(any())
}
@Test
@@ -90,7 +92,7 @@
startSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
@Test
@@ -101,7 +103,7 @@
startSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
@Test
@@ -111,7 +113,7 @@
startSideContent.layout(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
@Test
@@ -121,7 +123,7 @@
endSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener).onStatusBarBoundsChanged()
+ verify(boundsChangeListener).onStatusBarBoundsChanged(any())
}
@Test
@@ -130,7 +132,7 @@
endSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
@Test
@@ -141,7 +143,7 @@
endSideContent.setBoundsOnScreen(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
@Test
@@ -151,7 +153,7 @@
endSideContent.layout(newBounds)
- verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
deleted file mode 100644
index 0cd2214..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-package com.android.systemui.statusbar.phone
-
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Display
-import android.view.WindowInsets
-import android.view.WindowInsetsController
-import android.view.WindowInsetsController.*
-import androidx.test.filters.SmallTest
-import com.android.internal.statusbar.LetterboxDetails
-import com.android.internal.view.AppearanceRegion
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class SystemBarAttributesListenerTest : SysuiTestCase() {
-
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var lightBarController: LightBarController
- @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
- @Mock private lateinit var centralSurfaces: CentralSurfaces
-
- private lateinit var sysBarAttrsListener: SystemBarAttributesListener
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- whenever(
- letterboxAppearanceCalculator.getLetterboxAppearance(
- anyInt(), anyObject(), anyObject()))
- .thenReturn(TEST_LETTERBOX_APPEARANCE)
-
- sysBarAttrsListener =
- SystemBarAttributesListener(
- centralSurfaces,
- letterboxAppearanceCalculator,
- lightBarController,
- dumpManager)
- }
-
- @Test
- fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() {
- val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
-
- changeSysBarAttrs(appearance)
-
- verify(centralSurfaces).setAppearance(appearance)
- }
-
- @Test
- fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
-
- verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
- }
-
- @Test
- fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
- changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
-
- verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
- }
-
- @Test
- fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS)
-
- verify(lightBarController)
- .onStatusBarAppearanceChanged(
- eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean())
- }
-
- @Test
- fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
-
- verify(lightBarController)
- .onStatusBarAppearanceChanged(
- eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
- anyBoolean(),
- anyInt(),
- anyBoolean())
- }
-
- @Test
- fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
- reset(centralSurfaces, lightBarController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verify(lightBarController)
- .onStatusBarAppearanceChanged(
- eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
- anyBoolean(),
- anyInt(),
- anyBoolean())
- }
-
- @Test
- fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
- reset(centralSurfaces, lightBarController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
- }
-
- @Test
- fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
- reset(centralSurfaces, lightBarController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verifyZeroInteractions(centralSurfaces, lightBarController)
- }
-
- private fun changeSysBarAttrs(@Appearance appearance: Int) {
- changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
- }
-
- private fun changeSysBarAttrs(
- @Appearance appearance: Int,
- letterboxDetails: Array<LetterboxDetails>
- ) {
- changeSysBarAttrs(appearance, arrayOf(), letterboxDetails)
- }
-
- private fun changeSysBarAttrs(
- @Appearance appearance: Int,
- appearanceRegions: Array<AppearanceRegion>
- ) {
- changeSysBarAttrs(appearance, appearanceRegions, arrayOf())
- }
-
- private fun changeSysBarAttrs(
- @Appearance appearance: Int,
- appearanceRegions: Array<AppearanceRegion>,
- letterboxDetails: Array<LetterboxDetails>
- ) {
- sysBarAttrsListener.onSystemBarAttributesChanged(
- Display.DEFAULT_DISPLAY,
- appearance,
- appearanceRegions,
- /* navbarColorManagedByIme= */ false,
- WindowInsetsController.BEHAVIOR_DEFAULT,
- WindowInsets.Type.defaultVisible(),
- "package name",
- letterboxDetails)
- }
-
- companion object {
- private const val TEST_APPEARANCE =
- APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
- private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300))
- private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
- private val TEST_LETTERBOX_DETAILS =
- arrayOf(
- LetterboxDetails(
- /* letterboxInnerBounds= */ Rect(0, 0, 0, 0),
- /* letterboxFullBounds= */ Rect(0, 0, 0, 0),
- /* appAppearance= */ 0))
- private val TEST_LETTERBOX_APPEARANCE =
- LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf())
- }
-}
-
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 32320b4..49de512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -85,6 +86,7 @@
private val uiEventLoggerFake = UiEventLoggerFake()
private val testScope = TestScope()
private val statusBarModeRepository = FakeStatusBarModeRepository()
+ private val ongoingCallRepository = OngoingCallRepository()
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
@@ -111,6 +113,7 @@
controller = OngoingCallController(
testScope.backgroundScope,
context,
+ ongoingCallRepository,
notificationCollection,
clock,
mockActivityStarter,
@@ -140,10 +143,11 @@
}
@Test
- fun onEntryUpdated_isOngoingCallNotif_listenerNotified() {
+ fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
}
@Test
@@ -154,10 +158,11 @@
}
@Test
- fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
+ fun onEntryUpdated_notOngoingCallNotif_listenerAndRepoNotNotified() {
notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
}
@Test
@@ -169,6 +174,16 @@
.onOngoingCallStateChanged(anyBoolean())
}
+ @Test
+ fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+
+ notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
+
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ }
+
/** Regression test for b/191472854. */
@Test
fun onEntryUpdated_notifHasNullContentIntent_noCrash() {
@@ -276,6 +291,17 @@
}
@Test
+ fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ }
+
+ @Test
fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() {
val ongoingCallNotifEntry = createOngoingCallNotifEntry()
notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
@@ -302,6 +328,22 @@
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
+ /** Regression test for b/188491504. */
+ @Test
+ fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_repoUpdated() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+
+ // Create another notification based on the ongoing call one, but remove the features that
+ // made it a call notification.
+ val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
+ removedEntryBuilder.modifyNotification(context).style = null
+
+ notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
+
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+ }
+
@Test
fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
@@ -313,6 +355,15 @@
}
@Test
+ fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_repoNotUpdated() {
+ notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
+
+ notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
+
+ assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+ }
+
+ @Test
fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() {
assertThat(controller.hasOngoingCall()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
new file mode 100644
index 0000000..56aa7d6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class OngoingCallRepositoryTest : SysuiTestCase() {
+ private val underTest = OngoingCallRepository()
+
+ @Test
+ fun hasOngoingCall_matchesSet() {
+ underTest.setHasOngoingCall(true)
+
+ assertThat(underTest.hasOngoingCall.value).isTrue()
+
+ underTest.setHasOngoingCall(false)
+
+ assertThat(underTest.hasOngoingCall.value).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 15b9d61..c935dbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -17,13 +17,14 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
@@ -181,6 +182,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -192,6 +195,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -203,6 +208,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -214,6 +221,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -225,6 +234,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -236,6 +247,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -247,6 +260,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -258,6 +273,8 @@
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index a520f6c..49a2648 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,8 +18,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -136,6 +138,10 @@
// is used instead
assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+ assertThat(
+ (latest as WifiIcon.Visible).contentDescription.loadContentDescription(context)
+ )
+ .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 85359ca..bfc5bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -342,6 +342,31 @@
}
@Test
+ public void testShowToast_afterShowToast_animationListenerCleanup() throws RemoteException {
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback, Display.DEFAULT_DISPLAY);
+ final SystemUIToast toast = mToastUI.mToast;
+
+ View view = verifyWmAddViewAndAttachToParent();
+ mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG,
+ null, Display.DEFAULT_DISPLAY);
+
+ if (toast.getOutAnimation() != null) {
+ assertThat(mToastUI.mToastOutAnimatorListener).isNotNull();
+ assertThat(toast.getOutAnimation().getListeners()
+ .contains(mToastUI.mToastOutAnimatorListener)).isTrue();
+ assertThat(toast.getOutAnimation().isRunning()).isTrue();
+ toast.getOutAnimation().cancel(); // end early if applicable
+ assertThat(toast.getOutAnimation().getListeners()).isNull();
+ }
+
+ verify(mWindowManager).removeViewImmediate(view);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
+ verify(mCallback).onToastHidden();
+ assertThat(mToastUI.mToastOutAnimatorListener).isNull();
+ }
+
+ @Test
public void testShowToast_logs() {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback, Display.DEFAULT_DISPLAY);
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 65b8b55..424218c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -87,16 +87,19 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.BubbleIconFactory;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -104,11 +107,14 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeWindowLogger;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -121,15 +127,19 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -176,6 +186,8 @@
import java.util.List;
import java.util.Optional;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -299,6 +311,9 @@
@Mock
private Icon mAppBubbleIcon;
+ private SceneTestUtils mUtils = new SceneTestUtils(this);
+ private TestScope mTestScope = mUtils.getTestScope();
+ private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -335,6 +350,22 @@
when(mNotificationShadeWindowView.getViewTreeObserver())
.thenReturn(mock(ViewTreeObserver.class));
+ mShadeInteractor = new ShadeInteractor(
+ mTestScope.getBackgroundScope(),
+ new FakeDisableFlagsRepository(),
+ new FakeSceneContainerFlags(),
+ mUtils::sceneInteractor,
+ new FakeKeyguardRepository(),
+ new FakeUserSetupRepository(),
+ mock(DeviceProvisionedController.class),
+ mock(UserInteractor.class),
+ new SharedNotificationContainerInteractor(
+ new FakeConfigurationRepository(),
+ mContext,
+ new ResourcesSplitShadeStateController()),
+ new FakeShadeRepository()
+ );
+
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
@@ -352,7 +383,9 @@
mScreenOffAnimationController,
mAuthController,
mShadeExpansionStateManager,
- mShadeWindowLogger);
+ () -> mShadeInteractor,
+ mShadeWindowLogger
+ );
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index f2e4528..4fc3e3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -24,20 +24,19 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAuthenticationRepository(
+ private val deviceEntryRepository: FakeDeviceEntryRepository,
private val currentTime: () -> Long,
) : AuthenticationRepository {
private val _isAutoConfirmEnabled = MutableStateFlow(false)
override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
- private val _isUnlocked = MutableStateFlow(false)
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override val hintedPinLength: Int = HINTING_PIN_LENGTH
private val _isPatternVisible = MutableStateFlow(true)
@@ -53,7 +52,6 @@
override val minPatternLength: Int = 4
- private var isLockscreenEnabled = true
private var failedAttemptCount = 0
private var throttlingEndTimestamp = 0L
private var credentialOverride: List<Any>? = null
@@ -72,13 +70,9 @@
credentialOverride = pin
}
- override suspend fun isLockscreenEnabled(): Boolean {
- return isLockscreenEnabled
- }
-
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
- _isUnlocked.value = isSuccessful
+ deviceEntryRepository.setUnlocked(isSuccessful)
}
override suspend fun getPinLength(): Int {
@@ -97,18 +91,10 @@
_throttling.value = throttlingModel
}
- fun setUnlocked(isUnlocked: Boolean) {
- _isUnlocked.value = isUnlocked
- }
-
fun setAutoConfirmEnabled(isEnabled: Boolean) {
_isAutoConfirmEnabled.value = isEnabled
}
- fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
- this.isLockscreenEnabled = isLockscreenEnabled
- }
-
override suspend fun setThrottleDuration(durationMs: Int) {
throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
new file mode 100644
index 0000000..5e60a09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -0,0 +1,35 @@
+package com.android.systemui.deviceentry.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [DeviceEntryRepository] */
+class FakeDeviceEntryRepository : DeviceEntryRepository {
+
+ private var isInsecureLockscreenEnabled = true
+ private var isBypassEnabled = false
+
+ private val _isUnlocked = MutableStateFlow(false)
+ override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+ override fun isBypassEnabled(): Boolean {
+ return isBypassEnabled
+ }
+
+ override suspend fun isInsecureLockscreenEnabled(): Boolean {
+ return isInsecureLockscreenEnabled
+ }
+
+ fun setUnlocked(isUnlocked: Boolean) {
+ _isUnlocked.value = isUnlocked
+ }
+
+ fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) {
+ this.isInsecureLockscreenEnabled = isLockscreenEnabled
+ }
+
+ fun setBypassEnabled(isBypassEnabled: Boolean) {
+ this.isBypassEnabled = isBypassEnabled
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index a5f5d52..aa52609 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -63,9 +63,6 @@
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
- private val _isKeyguardUnlocked = MutableStateFlow(false)
- override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
-
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -150,11 +147,6 @@
return _isKeyguardShowing.value
}
- private var _isBypassEnabled = false
- override fun isBypassEnabled(): Boolean {
- return _isBypassEnabled
- }
-
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -252,14 +244,6 @@
_statusBarState.value = state
}
- fun setKeyguardUnlocked(isUnlocked: Boolean) {
- _isKeyguardUnlocked.value = isUnlocked
- }
-
- fun setBypassEnabled(isEnabled: Boolean) {
- _isBypassEnabled = isEnabled
- }
-
fun setScreenModel(screenModel: ScreenModel) {
_screenModel.value = screenModel
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 2e3bb2b..1cae09b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,6 +19,7 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -42,6 +43,7 @@
sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
commandQueue: FakeCommandQueue = FakeCommandQueue(),
+ deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -52,6 +54,7 @@
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -60,6 +63,7 @@
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -69,7 +73,7 @@
}
/** Provide defaults, otherwise tests will throw an error */
- fun createFakeFeatureFlags(): FakeFeatureFlags {
+ private fun createFakeFeatureFlags(): FakeFeatureFlags {
return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
}
@@ -78,6 +82,7 @@
val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
val sceneContainerFlags: SceneContainerFlags,
+ val deviceEntryRepository: FakeDeviceEntryRepository,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
new file mode 100644
index 0000000..f62bf60
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
+
+ var handleResult: Boolean = false
+ var policyResult: DisabledByPolicyInteractor.PolicyResult =
+ DisabledByPolicyInteractor.PolicyResult.TileEnabled
+
+ override suspend fun isDisabled(
+ userId: Int,
+ userRestriction: String?
+ ): DisabledByPolicyInteractor.PolicyResult = policyResult
+
+ override fun handlePolicyResult(
+ policyResult: DisabledByPolicyInteractor.PolicyResult
+ ): Boolean = handleResult
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 13437c9..1cb4ab7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import javax.annotation.CheckReturnValue
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 4e0266e..9c99cb5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 69c89e8..179206f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -34,6 +34,9 @@
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.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -73,16 +76,10 @@
val testScope = TestScope(testDispatcher)
val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
- private val userRepository: UserRepository by lazy {
- FakeUserRepository().apply {
- val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
- setUserInfos(users)
- runBlocking { setSelectedUserInfo(users.first()) }
- }
- }
-
+ val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
val authenticationRepository: FakeAuthenticationRepository by lazy {
FakeAuthenticationRepository(
+ deviceEntryRepository = deviceEntryRepository,
currentTime = { testScope.currentTime },
)
}
@@ -103,6 +100,14 @@
}
val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+ private val userRepository: UserRepository by lazy {
+ FakeUserRepository().apply {
+ val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
+ setUserInfos(users)
+ runBlocking { setSelectedUserInfo(users.first()) }
+ }
+ }
+
private val context = test.context
private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
@@ -145,31 +150,41 @@
)
}
- fun authenticationRepository(): FakeAuthenticationRepository {
- return authenticationRepository
+ fun deviceEntryInteractor(
+ repository: DeviceEntryRepository = deviceEntryRepository,
+ authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+ ): DeviceEntryInteractor {
+ return DeviceEntryInteractor(
+ applicationScope = applicationScope(),
+ repository = repository,
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
}
fun authenticationInteractor(
- repository: AuthenticationRepository,
- sceneInteractor: SceneInteractor = sceneInteractor(),
+ repository: AuthenticationRepository = authenticationRepository,
): AuthenticationInteractor {
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
backgroundDispatcher = testDispatcher,
userRepository = userRepository,
- keyguardRepository = keyguardRepository,
- sceneInteractor = sceneInteractor,
+ deviceEntryRepository = deviceEntryRepository,
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
- fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+ fun keyguardInteractor(
+ repository: KeyguardRepository = keyguardRepository
+ ): KeyguardInteractor {
return KeyguardInteractor(
repository = repository,
commandQueue = FakeCommandQueue(),
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = FakeDeviceEntryRepository(),
bouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository = FakeConfigurationRepository(),
shadeRepository = FakeShadeRepository(),
@@ -185,6 +200,7 @@
}
fun bouncerInteractor(
+ deviceEntryInteractor: DeviceEntryInteractor,
authenticationInteractor: AuthenticationInteractor,
sceneInteractor: SceneInteractor,
): BouncerInteractor {
@@ -192,6 +208,7 @@
applicationScope = applicationScope(),
applicationContext = context,
repository = BouncerRepository(),
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
flags = sceneContainerFlags,
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 25f57b3..fb8db21 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -19,7 +19,10 @@
java_library_static {
name: "services.companion",
defaults: ["platform_service_defaults"],
- srcs: [":services.companion-sources"],
+ srcs: [
+ ":services.companion-sources",
+ ":VirtualCamera-aidl-sources",
+ ],
libs: [
"app-compat-annotations",
"services.core",
@@ -29,3 +32,11 @@
"virtualdevice_flags_lib",
],
}
+
+filegroup {
+ name: "VirtualCamera-aidl-sources",
+ srcs: [
+ "java/com/android/server/companion/virtual/camera/*.aidl",
+ ],
+ path: "java",
+}
diff --git a/services/companion/OWNERS b/services/companion/OWNERS
index 734d8b6..dcf2377 100644
--- a/services/companion/OWNERS
+++ b/services/companion/OWNERS
@@ -1 +1,3 @@
-include /core/java/android/companion/OWNERS
\ No newline at end of file
+include /core/java/android/companion/OWNERS
+
+per-file Android.bp,lint-baseline.xml,OWNERS=file:java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 253ef43..3583a78 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -39,7 +39,7 @@
"include-filter": "android.hardware.input.cts.tests"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
],
"file_patterns": ["Virtual[^/]*\\.java"]
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index a2e4d2c..8a2aa61 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -50,6 +50,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.camera.IVirtualCamera;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
@@ -105,6 +106,7 @@
import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -114,7 +116,6 @@
import java.util.Set;
import java.util.function.Consumer;
-
final class VirtualDeviceImpl extends IVirtualDevice.Stub
implements IBinder.DeathRecipient, RunningAppsChangedListener {
@@ -178,6 +179,8 @@
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
+ @Nullable // Null if virtual camera flag is off.
+ private final VirtualCameraController mVirtualCameraController;
private VirtualAudioController mVirtualAudioController;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
@@ -270,7 +273,8 @@
soundEffectListener,
runningAppsChangedCallback,
params,
- DisplayManagerGlobal.getInstance());
+ DisplayManagerGlobal.getInstance(),
+ Flags.virtualCamera() ? new VirtualCameraController(context) : null);
}
@VisibleForTesting
@@ -289,7 +293,8 @@
IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params,
- DisplayManagerGlobal displayManager) {
+ DisplayManagerGlobal displayManager,
+ VirtualCameraController virtualCameraController) {
super(PermissionEnforcer.fromContext(context));
mVirtualDeviceLog = virtualDeviceLog;
mOwnerPackageName = attributionSource.getPackageName();
@@ -324,6 +329,7 @@
} else {
mPermissionDialogComponent = null;
}
+ mVirtualCameraController = virtualCameraController;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -564,6 +570,9 @@
} finally {
Binder.restoreCallingIdentity(ident);
}
+ if (mVirtualCameraController != null) {
+ mVirtualCameraController.close();
+ }
}
@Override
@@ -920,24 +929,37 @@
}
}
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void registerVirtualCamera(@NonNull IVirtualCamera camera) {
+ super.registerVirtualCamera_enforcePermission();
+ if (mVirtualCameraController == null) {
+ return;
+ }
+ mVirtualCameraController.registerCamera(Objects.requireNonNull(camera));
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ String indent = " ";
fout.println(" VirtualDevice: ");
- fout.println(" mDeviceId: " + mDeviceId);
- fout.println(" mAssociationId: " + mAssociationInfo.getId());
- fout.println(" mOwnerPackageName: " + mOwnerPackageName);
- fout.println(" mParams: ");
- mParams.dump(fout, " ");
- fout.println(" mVirtualDisplayIds: ");
+ fout.println(indent + "mDeviceId: " + mDeviceId);
+ fout.println(indent + "mAssociationId: " + mAssociationInfo.getId());
+ fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName);
+ fout.println(indent + "mParams: ");
+ mParams.dump(fout, indent + indent);
+ fout.println(indent + "mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
fout.println(" mDevicePolicies: " + mDevicePolicies);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
- fout.println(" " + mVirtualDisplays.keyAt(i));
+ fout.println(indent + " " + mVirtualDisplays.keyAt(i));
}
- fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
+ fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
mSensorController.dump(fout);
+ if (mVirtualCameraController != null) {
+ mVirtualCameraController.dump(fout, indent);
+ }
}
@GuardedBy("mVirtualDeviceLock")
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
new file mode 100644
index 0000000..a4c1c42
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+
+/**
+ * AIDL Interface to communicate with the VirtualCamera HAL
+ * @hide
+ */
+interface IVirtualCameraService {
+
+ /**
+ * Registers a new camera with the virtual camera hal.
+ * @return true if the camera was successfully registered
+ */
+ boolean registerCamera(in IVirtualCamera camera);
+
+ /**
+ * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't
+ * be visible to the camera framework anymore.
+ */
+ void unregisterCamera(in IVirtualCamera camera);
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
new file mode 100644
index 0000000..031d949
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages the registration and removal of virtual camera from the server side.
+ *
+ * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service
+ * to be up and running
+ */
+public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection {
+
+ private static class VirtualCameraInfo {
+
+ private final IVirtualCamera mVirtualCamera;
+ private boolean mIsRegistered;
+
+ VirtualCameraInfo(IVirtualCamera virtualCamera) {
+ mVirtualCamera = virtualCamera;
+ }
+ }
+
+ private static final String TAG = "VirtualCameraController";
+
+ private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera";
+ private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService";
+ private final Context mContext;
+
+ @Nullable private IVirtualCameraService mVirtualCameraService = null;
+
+ @GuardedBy("mCameras")
+ private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1);
+
+ public VirtualCameraController(Context context) {
+ mContext = context;
+ connectVirtualCameraService();
+ }
+
+ private void connectVirtualCameraService() {
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent();
+ intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE);
+ intent.setComponent(
+ ComponentName.createRelative(
+ VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS));
+ mContext.startServiceAsUser(intent, UserHandle.SYSTEM);
+ if (!mContext.bindServiceAsUser(
+ intent,
+ this,
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+ UserHandle.SYSTEM)) {
+ mContext.unbindService(this);
+ Log.w(
+ TAG,
+ "connectVirtualCameraService: Failed to connect to the virtual camera "
+ + "service");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ private void forwardPendingRegistrations() {
+ IVirtualCameraService cameraService = mVirtualCameraService;
+ if (cameraService == null) {
+ return;
+ }
+ synchronized (mCameras) {
+ for (VirtualCameraInfo cameraInfo : mCameras.values()) {
+ if (cameraInfo.mIsRegistered) {
+ continue;
+ }
+ try {
+ cameraService.registerCamera(cameraInfo.mVirtualCamera);
+ cameraInfo.mIsRegistered = true;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the virtual camera with the provided name
+ *
+ * @param camera The name of the camera to remove
+ */
+ public void unregisterCamera(@NonNull IVirtualCamera camera) {
+ IVirtualCameraService virtualCameraService = mVirtualCameraService;
+ if (virtualCameraService != null) {
+ try {
+ virtualCameraService.unregisterCamera(camera);
+ synchronized (mCameras) {
+ VirtualCameraInfo cameraInfo = mCameras.remove(camera);
+ cameraInfo.mIsRegistered = false;
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Register a new virtual camera with the provided characteristics.
+ *
+ * @param camera The {@link IVirtualCamera} producing the image to communicate with the client.
+ * @throws IllegalArgumentException if the characteristics could not be parsed.
+ */
+ public void registerCamera(@NonNull IVirtualCamera camera) {
+ IVirtualCameraService service = mVirtualCameraService;
+ VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera);
+ synchronized (mCameras) {
+ mCameras.put(camera, virtualCameraInfo);
+ }
+ if (service != null) {
+ try {
+ if (service.registerCamera(camera)) {
+ virtualCameraInfo.mIsRegistered = true;
+ return;
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ // Service was not available or registration failed, save the registration for later
+ connectVirtualCameraService();
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "binderDied");
+ mVirtualCameraService = null;
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ mVirtualCameraService = null;
+ Log.d(TAG, "onBindingDied() called with: name = [" + name + "]");
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ mVirtualCameraService = null;
+ Log.d(TAG, "onNullBinding() called with: name = [" + name + "]");
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected: " + name.toString());
+ mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service);
+ try {
+ service.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ forwardPendingRegistrations();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]");
+ mVirtualCameraService = null;
+ }
+
+ /** Release resources associated with this controller. */
+ public void close() {
+ if (mVirtualCameraService == null) {
+ return;
+ }
+ synchronized (mCameras) {
+ mCameras.forEach(
+ (name, cameraInfo) -> {
+ try {
+ mVirtualCameraService.unregisterCamera(name);
+ } catch (RemoteException e) {
+ Log.w(
+ TAG,
+ "close(): Camera failed to be removed on camera service.",
+ e);
+ }
+ });
+ }
+ mContext.unbindService(this);
+ }
+
+ /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
+ public void dump(PrintWriter fout, String indent) {
+ fout.println(indent + "VirtualCameraController:");
+ indent += indent;
+ fout.printf("%sService:%s\n", indent, mVirtualCameraService);
+ synchronized (mCameras) {
+ fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
+ for (VirtualCameraInfo info : mCameras.values()) {
+ VirtualCameraHalConfig config = null;
+ try {
+ config = info.mVirtualCamera.getHalConfig();
+ } catch (RemoteException ex) {
+ Log.w(TAG, ex);
+ }
+ fout.printf(
+ "%s- %s isRegistered: %s, token: %s\n",
+ indent,
+ config == null ? "" : config.displayName,
+ info.mIsRegistered,
+ info.mVirtualCamera);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 2a46d86..a1e6b58f 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) {
+ IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -94,9 +94,13 @@
filter.dump(logPrinter, " ");
}
- final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
+ 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/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index d6f1348..ced7773 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -342,7 +342,10 @@
// Check if user is associated with the subscription
if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
- Binder.getCallingUserHandle())) {
+ Binder.getCallingUserHandle())
+ // For inactive sub, fall through to MMS service to have it recorded in metrics.
+ && isActiveSubId(subId)) {
+ // Try remind user to use another profile to send.
TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext,
subId, Binder.getCallingUid(), callingPkg);
return;
@@ -550,6 +553,17 @@
}
}
+ /** @return true if the subId is active. */
+ private boolean isActiveSubId(int subId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
+ return subManager != null && subManager.isActiveSubscriptionId(subId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private int getPhoneIdFromSubId(int subId) {
SubscriptionManager subManager = (SubscriptionManager)
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e17424b..6e984bb 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,6 +250,7 @@
private Monitor mCurrentMonitor;
private long mStartTimeMillis;
private int mPauseCount;
+ private long mOneOffTimeoutMillis;
HandlerChecker(Handler handler, String name) {
mHandler = handler;
@@ -269,7 +270,13 @@
* @param handlerCheckerTimeoutMillis the timeout to use for this run
*/
public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
- mWaitMaxMillis = handlerCheckerTimeoutMillis;
+ if (mOneOffTimeoutMillis > 0) {
+ mWaitMaxMillis = mOneOffTimeoutMillis;
+ mOneOffTimeoutMillis = 0;
+ } else {
+ mWaitMaxMillis = handlerCheckerTimeoutMillis;
+ }
+
if (mCompleted) {
// Safe to update monitors in queue, Handler is not in the middle of work
mMonitors.addAll(mMonitorQueue);
@@ -352,6 +359,23 @@
}
}
+ /**
+ * Sets the timeout of the HandlerChecker for one run.
+ *
+ * <p>The current run will be ignored and the next run will be set to this timeout.
+ *
+ * <p>If a one off timeout is already set, the maximum timeout will be used.
+ */
+ public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
+ mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+ // Mark as completed, because there's a chance we called this after the watchog
+ // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
+ // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+ mCompleted = true;
+ Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
+ + reason + ". New timeout: " + mOneOffTimeoutMillis);
+ }
+
/** Pause the HandlerChecker. */
public void pauseLocked(String reason) {
mPauseCount++;
@@ -598,6 +622,27 @@
}
}
+ /**
+ * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+ * to run a slow operation on one of the monitored thread.
+ *
+ * <p>After the next run, the timeout will go back to the default value.
+ *
+ * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
+ *
+ * <p>If a one-off timeout for the current thread is already, the max value will be used.
+ */
+ public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+ synchronized (mLock) {
+ for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
+ HandlerChecker checker = hc.checker();
+ if (Thread.currentThread().equals(checker.getThread())) {
+ checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+ }
+ }
+ }
+ }
+
/**
* Pauses Watchdog action for the currently running thread. Useful before executing long running
* operations that could falsely trigger the watchdog. Each call to this will require a matching
@@ -609,7 +654,7 @@
* adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
*
* <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
- * pauses have been resumed.
+ * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
*/
public void pauseWatchingCurrentThread(String reason) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 2c83c6f..6cca130 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -150,16 +150,7 @@
public boolean isChangeEnabledByUid(long changeId, int uid) {
super.isChangeEnabledByUid_enforcePermission();
- String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages == null || packages.length == 0) {
- return mCompatConfig.defaultChangeIdValue(changeId);
- }
- boolean enabled = true;
- for (String packageName : packages) {
- enabled &= isChangeEnabledByPackageName(changeId, packageName,
- UserHandle.getUserId(uid));
- }
- return enabled;
+ return isChangeEnabledByUidInternal(changeId, uid);
}
/**
@@ -208,6 +199,25 @@
return false;
}
+ /**
+ * Internal version of {@link #isChangeEnabledByUid(long, int)}.
+ *
+ * <p>Does not perform costly permission check.
+ */
+ public boolean isChangeEnabledByUidInternal(long changeId, int uid) {
+ String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return mCompatConfig.defaultChangeIdValue(changeId);
+ }
+ boolean enabled = true;
+ final int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ final var appInfo = getApplicationInfo(packageName, userId);
+ enabled &= isChangeEnabledInternal(changeId, appInfo);
+ }
+ return enabled;
+ }
+
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e942c17..d372f30 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -142,6 +142,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -548,6 +549,9 @@
@VisibleForTesting
DisplayManagerService(Context context, Injector injector) {
super(context);
+ FoldSettingProvider foldSettingProvider = new FoldSettingProvider(context,
+ new SettingsWrapper(),
+ new FoldLockSettingAvailabilityProvider(context.getResources()));
mInjector = injector;
mContext = context;
mFlags = injector.getFlags();
@@ -555,8 +559,8 @@
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
- new FoldSettingProvider(mContext, new SettingsWrapper()), mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
+ foldSettingProvider,
+ mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 6f243e1..049b2fd 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "DisplayServiceTests",
"options": [
{"include-filter": "com.android.server.display"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index e46edd9..652e6cf 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,6 +24,7 @@
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.BrightnessUtils;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -178,8 +179,12 @@
debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
}
+
+ float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
+ float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
+
mDesiredTransitionRate = Math.abs(
- (mMaxBrightness - mDesiredMaxBrightness) * 1000f / transitionDuration);
+ (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
mHandler.removeCallbacks(mDebouncer);
mHandler.postDelayed(mDebouncer, debounceTime);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 41053e9..68848a2 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -16,36 +16,66 @@
package com.android.server.grammaticalinflection;
+import static android.app.Flags.systemTermsOfAddressEnabled;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import android.annotation.Nullable;
+import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
-import android.os.IBinder;
+import android.os.Environment;
import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.util.AtomicFile;
import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.Xml;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
/**
* The implementation of IGrammaticalInflectionManager.aidl.
*
* <p>This service is API entry point for storing app-specific grammatical inflection.
*/
public class GrammaticalInflectionService extends SystemService {
- private final String TAG = "GrammaticalInflection";
- private final GrammaticalInflectionBackupHelper mBackupHelper;
- private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
- private PackageManagerInternal mPackageManagerInternal;
+ private static final String TAG = "GrammaticalInflection";
+ private static final String ATTR_NAME = "grammatical_gender";
+ private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml";
+ private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
private static final String GRAMMATICAL_INFLECTION_ENABLED =
"i18n.grammatical_Inflection.enabled";
+ private final GrammaticalInflectionBackupHelper mBackupHelper;
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private final Object mLock = new Object();
+ private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray();
+
+ private PackageManagerInternal mPackageManagerInternal;
+ private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+
/**
* Initializes the system service.
* <p>
@@ -62,22 +92,46 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBackupHelper = new GrammaticalInflectionBackupHelper(
this, context.getPackageManager());
+ mBinderService = new GrammaticalInflectionBinderService();
}
@Override
public void onStart() {
- publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+ publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService);
LocalServices.addService(GrammaticalInflectionManagerInternal.class,
new GrammaticalInflectionManagerInternalImpl());
}
- private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+ private final class GrammaticalInflectionBinderService extends
+ IGrammaticalInflectionManager.Stub {
@Override
public void setRequestedApplicationGrammaticalGender(
String appPackageName, int userId, int gender) {
GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
appPackageName, userId, gender);
}
+
+ @Override
+ public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+ checkCallerIsSystem();
+ checkSystemTermsOfAddressIsEnabled();
+ GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
+ userId);
+ }
+
+ @Override
+ public int getSystemGrammaticalGender(int userId) {
+ checkSystemTermsOfAddressIsEnabled();
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new GrammaticalInflectionShellCommand(mBinderService))
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
};
private final class GrammaticalInflectionManagerInternalImpl
@@ -94,12 +148,6 @@
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
}
-
- private void checkCallerIsSystem() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Caller is not system.");
- }
- }
}
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -137,4 +185,105 @@
updater.setGrammaticalGender(gender).commit();
}
+
+ protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+ if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(grammaticalGender, stream));
+ atomicFile.finishWrite(stream);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
+ // gender.
+ public int getSystemGrammaticalGender(int userId) {
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ if (!file.exists()) {
+ Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+ try {
+ InputStream in = new FileInputStream(file);
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+ }
+ return mGrammaticalGenderCache.get(userId);
+ }
+ }
+
+ private File getGrammaticalGenderFile(int userId) {
+ final File dir = new File(Environment.getDataSystemCeDirectory(userId),
+ TAG_GRAMMATICAL_INFLECTION);
+ return new File(dir, USER_SETTINGS_FILE_NAME);
+ }
+
+ private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
+
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+ out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.attributeInt(null, ATTR_NAME, grammaticalGender);
+ out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.endDocument();
+
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) {
+ return parser.getAttributeInt(null, ATTR_NAME);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ private void checkCallerIsSystem() {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
+ throw new SecurityException("Caller is not system and shell.");
+ }
+ }
+
+ private void checkSystemTermsOfAddressIsEnabled() {
+ if (!systemTermsOfAddressEnabled()) {
+ throw new RuntimeException("The flag must be enabled to allow calling the API.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
new file mode 100644
index 0000000..d223728
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.ActivityManager;
+import android.app.GrammaticalInflectionManager;
+import android.app.IGrammaticalInflectionManager;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link GrammaticalInflectionService}
+ */
+class GrammaticalInflectionShellCommand extends ShellCommand {
+
+ private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>();
+ static {
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ "Not specified (0)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)");
+ }
+
+ private final IGrammaticalInflectionManager mBinderService;
+
+ GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+ mBinderService = grammaticalInflectionManager;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ switch (cmd) {
+ case "set-system-grammatical-gender":
+ return runSetSystemWideGrammaticalGender();
+ case "get-system-grammatical-gender":
+ return runGetSystemGrammaticalGender();
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(
+ " set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender "
+ + "<GRAMMATICAL_GENDER>]");
+ pw.println(" Set the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ pw.println(
+ " --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user "
+ + "preferred in system, not specified (0) is used when unspecified.");
+ pw.println(
+ " eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine"
+ + ".");
+ pw.println(
+ " get-system-grammatical-gender [--user <USER_ID>]");
+ pw.println(" Get the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ }
+
+ private int runSetSystemWideGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ case "-g":
+ case "--grammaticalGender": {
+ grammaticalGender = parseGrammaticalGender();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int runGetSystemGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+ getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int parseGrammaticalGender() {
+ String arg = getNextArg();
+ if (arg == null) {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ } else {
+ int grammaticalGender = Integer.parseInt(arg);
+ if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ return grammaticalGender;
+ } else {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
index 5f16ba9..41d079e 100644
--- a/services/core/java/com/android/server/grammaticalinflection/OWNERS
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -2,3 +2,4 @@
allenwtsu@google.com
goldmanj@google.com
calvinpan@google.com
+zoeychen@google.com
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
index f868ea0..17b98ce8 100644
--- a/services/core/java/com/android/server/lights/TEST_MAPPING
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -4,16 +4,14 @@
"name": "CtsHardwareTestCases",
"options": [
{"include-filter": "com.android.hardware.lights"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.lights"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index b881b44..ddf3d76 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
@@ -20,7 +20,7 @@
"include-filter": "com.android.server.locksettings."
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
index f4b13a0..9041552 100644
--- a/services/core/java/com/android/server/logcat/TEST_MAPPING
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.logcat"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index a792498..7aa9118 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "MediaProjectionTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index 7db2e8b..468c451 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsNotificationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -14,9 +11,6 @@
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
},
@@ -24,9 +18,6 @@
"name": "FrameworksUiServicesTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -34,9 +25,6 @@
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 6baa889..5d2944e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -580,7 +580,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, filterCallingUid);
+ list, false, intent, resolvedType, flags, filterCallingUid);
}
}
} else {
@@ -610,7 +610,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, filterCallingUid);
+ list, false, originalIntent, resolvedType, flags, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -699,7 +699,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, callingUid);
+ list, false, intent, resolvedType, flags, callingUid);
}
}
} else {
@@ -711,7 +711,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, callingUid);
+ list, false, originalIntent, resolvedType, flags, callingUid);
}
return list;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 38f241d..8e91f42 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -44,6 +44,7 @@
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -69,6 +70,7 @@
import android.os.FileUtils;
import android.os.Process;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
import android.os.incremental.V4Signature;
@@ -180,8 +182,9 @@
public @interface SharedUserIdJoinType {}
/**
- * Components of apps targeting Android T and above will stop receiving intents from
- * external callers that do not match its declared intent filters.
+ * Intents sent from apps targeting Android V and above will stop resolving to components with
+ * non matching intent filters, even when explicitly setting a component name, unless the
+ * target components are in the same app as the calling app.
*
* When an app registers an exported component in its manifest and adds an <intent-filter>,
* the component can be started by any intent - even those that do not match the intent filter.
@@ -189,8 +192,9 @@
* Without checking the intent when the component is started, in some circumstances this can
* allow 3P apps to trigger internal-only functionality.
*/
+ @Overridable
@ChangeId
- @Disabled /* Revert enforcement: b/274147456 */
+ @Disabled /* Enforcement reverted in T: b/274147456 */
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1187,19 +1191,27 @@
public static void applyEnforceIntentFilterMatching(
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
- Intent intent, String resolvedType, int filterCallingUid) {
+ Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
+ int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+ // Do not enforce filter matching when the caller is system or root
+ if (ActivityManager.canAccessUnexportedComponents(filterCallingUid)) return;
+
final Printer logPrinter = DEBUG_INTENT_MATCHING
? 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);
+
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
- // Do not enforce filter matching when the caller is system, root, or the same app
- if (ActivityManager.checkComponentPermission(null, filterCallingUid,
- info.applicationInfo.uid, false) == PackageManager.PERMISSION_GRANTED) {
+ // Skip filter matching when the caller is targeting the same app
+ if (UserHandle.isSameApp(filterCallingUid, info.applicationInfo.uid)) {
continue;
}
@@ -1221,14 +1233,11 @@
continue;
}
- // Only enforce filter matching if target app's target SDK >= T
- final boolean enforce = compat.isChangeEnabledInternal(
- ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo);
-
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)) {
+ if (IntentResolver.intentMatchesFilter(
+ intentFilter, intent, resolvedType, defaultOnly)) {
match = true;
break;
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 160b2aa..da14397 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, filterCallingUid);
+ resolvedType, flags, 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, filterCallingUid);
+ list, true, originalIntent, resolvedType, flags, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 390c45b..0fd7a37 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -55,6 +55,7 @@
},
{
"name": "GtsContentTestCases",
+ "keywords": ["internal"],
"options": [
{
"include-filter": "com.google.android.content.gts"
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 819a82c..338b479 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
@@ -49,7 +49,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.SplitPermissionTest"
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4a4214f..dfbcbae6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3347,6 +3347,8 @@
} else {
startDreaming = false;
}
+ Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming
+ + " wakefulness=" + wakefulnessToString(wakefulness));
}
// Start dreaming if needed.
@@ -3381,19 +3383,23 @@
if (startDreaming && isDreaming) {
mDreamsBatteryLevelDrain = 0;
if (wakefulness == WAKEFULNESS_DOZING) {
- Slog.i(TAG, "Dozing...");
+ Slog.i(TAG, "Dozing powerGroup " + groupId);
} else {
- Slog.i(TAG, "Dreaming...");
+ Slog.i(TAG, "Dreaming powerGroup " + groupId);
}
}
// If preconditions changed, wait for the next iteration to determine
// whether the dream should continue (or be restarted).
final PowerGroup powerGroup = mPowerGroups.get(groupId);
+ final int newWakefulness = powerGroup.getWakefulnessLocked();
if (powerGroup.isSandmanSummonedLocked()
- || powerGroup.getWakefulnessLocked() != wakefulness) {
+ || newWakefulness != wakefulness) {
return; // wait for next cycle
}
+ Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming
+ + " wakefulness=" + newWakefulness);
+
// Determine whether the dream should continue.
long now = mClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index 19086a1..05a0e85 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -3,16 +3,14 @@
{
"name": "CtsBatterySavingTestCases",
"options": [
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.power"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -20,7 +18,6 @@
"name": "PowerServiceTests",
"options": [
{"include-filter": "com.android.server.power"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 88eaafa..3fd8323 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2115,7 +2115,7 @@
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+ bar.requestAddTile(callingUid, componentName, appName, label, icon, proxyCallback);
return;
} catch (RemoteException e) {
Slog.e(TAG, "requestAddTile", e);
diff --git a/services/core/java/com/android/server/utils/FoldSettingProvider.java b/services/core/java/com/android/server/utils/FoldSettingProvider.java
index d62628b..552343a 100644
--- a/services/core/java/com/android/server/utils/FoldSettingProvider.java
+++ b/services/core/java/com/android/server/utils/FoldSettingProvider.java
@@ -22,7 +22,7 @@
import android.provider.Settings;
import android.util.Log;
-import com.android.internal.R;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
import com.android.internal.util.SettingsWrapper;
import java.util.Set;
@@ -51,14 +51,14 @@
private static final String TAG = "FoldSettingProvider";
private final ContentResolver mContentResolver;
- private final boolean mIsFoldLockBehaviorAvailable;
private final SettingsWrapper mSettingsWrapper;
+ private final FoldLockSettingAvailabilityProvider mFoldLockSettingAvailabilityProvider;
- public FoldSettingProvider(Context context, SettingsWrapper settingsWrapper) {
+ public FoldSettingProvider(Context context, SettingsWrapper settingsWrapper,
+ FoldLockSettingAvailabilityProvider foldLockSettingAvailabilityProvider) {
mContentResolver = context.getContentResolver();
mSettingsWrapper = settingsWrapper;
- mIsFoldLockBehaviorAvailable = context.getResources().getBoolean(
- R.bool.config_fold_lock_behavior);
+ mFoldLockSettingAvailabilityProvider = foldLockSettingAvailabilityProvider;
}
/**
@@ -83,7 +83,9 @@
}
private String getFoldSettingValue() {
- if (!mIsFoldLockBehaviorAvailable) {
+ boolean isFoldLockBehaviorAvailable =
+ mFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable();
+ if (!isFoldLockBehaviorAvailable) {
return SETTING_VALUE_DEFAULT;
}
String foldSettingValue = mSettingsWrapper.getStringForUser(
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4c525e9..01ea33f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -28,6 +28,7 @@
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -74,6 +75,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
+import android.multiuser.Flags;
import android.os.Binder;
import android.os.Bundle;
import android.os.FileObserver;
@@ -114,6 +116,7 @@
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -132,6 +135,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -746,6 +750,8 @@
}
private final Context mContext;
+ private final AtomicBoolean mIsInitialBinding = new AtomicBoolean(true);
+ private final ServiceThread mHandlerThread;
private final WindowManagerInternal mWindowManagerInternal;
private final PackageManagerInternal mPackageManagerInternal;
private final IPackageManager mIPackageManager;
@@ -1619,6 +1625,12 @@
public WallpaperManagerService(Context context) {
if (DEBUG) Slog.v(TAG, "WallpaperService startup");
mContext = context;
+ if (Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
+ mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
+ mHandlerThread.start();
+ } else {
+ mHandlerThread = null;
+ }
mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
@@ -3652,7 +3664,10 @@
com.android.internal.R.bool.config_wallpaperTopApp)) {
bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
}
- boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
+ Handler handler = Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()
+ && !mIsInitialBinding.compareAndSet(true, false)
+ ? mHandlerThread.getThreadHandler() : mContext.getMainThreadHandler();
+ boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, handler,
new UserHandle(serviceUserId));
if (!bindSuccess) {
String msg = "Unable to bind service: " + componentName;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 442269a..10405ec 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -302,12 +302,9 @@
}
// Always update the reordering time when this is called to ensure that the timeout
- // is reset. Extend this duration when running in tests.
- final long timeout = ActivityManager.isRunningInUserTestHarness()
- ? mFreezeTaskListTimeoutMs * 10
- : mFreezeTaskListTimeoutMs;
+ // is reset
mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
- mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
+ mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
}
/**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2a33918..ea5c9c2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2234,9 +2234,11 @@
mService.getTaskChangeNotificationController().notifyActivityUnpinned();
}
mWindowManager.mPolicy.setPipVisibilityLw(inPip);
- mWmService.mTransactionFactory.get()
- .setTrustedOverlay(task.getSurfaceControl(), inPip)
- .apply();
+ if (task.getSurfaceControl() != null) {
+ mWmService.mTransactionFactory.get()
+ .setTrustedOverlay(task.getSurfaceControl(), inPip)
+ .apply();
+ }
}
void executeAppTransitionForAllDisplay() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 261fc2e..8385615 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1162,11 +1162,17 @@
forAllActivities(oldParentTask::cleanUpActivityReferences);
}
- if (oldParent.inPinnedWindowingMode()
- && (newParent == null || !newParent.inPinnedWindowingMode())) {
- // Notify if a task from the root pinned task is being removed
- // (or moved depending on the mode).
- mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+ if (newParent == null || !newParent.inPinnedWindowingMode()) {
+ if (oldParent.inPinnedWindowingMode()) {
+ // Notify if a task from the root pinned task is being removed
+ // (or moved depending on the mode).
+ mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+ } else if (inPinnedWindowingMode()) {
+ // The task in pinned mode is removed, even though the old parent was not pinned
+ // The task was most likely force killed or crashed
+ Slog.e(TAG, "Pinned task is removed t=" + this);
+ mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d95bc7..50bc825 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -255,6 +255,11 @@
boolean mClearedForReorderActivityToFront;
/**
+ * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}.
+ */
+ boolean mIsSurfaceManagedBySystemOrganizer = false;
+
+ /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -449,13 +454,21 @@
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@NonNull String processName) {
+ setTaskFragmentOrganizer(organizer, uid, processName,
+ false /* isSurfaceManagedBySystemOrganizer */);
+ }
+
+ void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+ @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) {
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
mTaskFragmentOrganizerUid = uid;
mTaskFragmentOrganizerProcessName = processName;
+ mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer;
}
void onTaskFragmentOrganizerRemoved() {
mTaskFragmentOrganizer = null;
+ mIsSurfaceManagedBySystemOrganizer = false;
}
/** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2396,6 +2409,9 @@
if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
return;
}
+ if (mIsSurfaceManagedBySystemOrganizer) {
+ return;
+ }
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.isCollecting(this)) {
// TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index ea722b6..04164c2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -26,6 +26,7 @@
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
@@ -50,12 +51,15 @@
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -133,6 +137,13 @@
new WeakHashMap<>();
/**
+ * Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true,
+ * the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the
+ * client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event.
+ */
+ private final boolean mIsSystemOrganizer;
+
+ /**
* {@link RemoteAnimationDefinition} for embedded activities transition animation that is
* organized by this organizer.
*/
@@ -147,10 +158,12 @@
*/
private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
- TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
+ TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
+ boolean isSystemOrganizer) {
mOrganizer = organizer;
mOrganizerPid = pid;
mOrganizerUid = uid;
+ mIsSystemOrganizer = isSystemOrganizer;
try {
mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
} catch (RemoteException e) {
@@ -235,11 +248,15 @@
tf.mTaskFragmentAppearedSent = true;
mLastSentTaskFragmentInfos.put(tf, info);
mTaskFragmentTaskIds.put(tf, taskId);
- return new TaskFragmentTransaction.Change(
+ final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_APPEARED)
.setTaskFragmentToken(tf.getFragmentToken())
.setTaskFragmentInfo(info)
.setTaskId(taskId);
+ if (mIsSystemOrganizer) {
+ change.setTaskFragmentSurfaceControl(tf.getSurfaceControl());
+ }
+ return change;
}
@NonNull
@@ -435,8 +452,25 @@
: null;
}
+ @VisibleForTesting
+ void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ registerOrganizerInternal(organizer, false /* isSystemOrganizer */);
+ }
+
@Override
- public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ public void registerOrganizer(
+ @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+ registerOrganizerInternal(
+ organizer,
+ Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer);
+ }
+
+ @VisibleForTesting
+ void registerOrganizerInternal(
+ @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+ if (isSystemOrganizer) {
+ enforceTaskPermission("registerSystemOrganizer()");
+ }
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
@@ -448,7 +482,7 @@
"Replacing existing organizer currently unsupported");
}
mTaskFragmentOrganizerState.put(organizer.asBinder(),
- new TaskFragmentOrganizerState(organizer, pid, uid));
+ new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer));
mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>());
}
}
@@ -711,6 +745,12 @@
}
}
+ boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(token.asBinder());
+ return state != null && state.mIsSystemOrganizer;
+ }
+
@Nullable
private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
@NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3faf8b9..3db7765 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9646,15 +9646,4 @@
Binder.restoreCallingIdentity(origId);
}
}
-
- /**
- * Resets the spatial ordering of recents for testing purposes.
- */
- void resetFreezeRecentTaskListReordering() {
- if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS,
- "resetFreezeRecentTaskListReordering()")) {
- throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
- }
- mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout();
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index fa9a65f..8fad950 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -142,8 +142,6 @@
return runReset(pw);
case "disable-blur":
return runSetBlurDisabled(pw);
- case "reset-freeze-recent-tasks":
- return runResetFreezeRecentTaskListReordering(pw);
case "shell":
return runWmShellCommand(pw);
default:
@@ -254,11 +252,6 @@
return 0;
}
- private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException {
- mInternal.resetFreezeRecentTaskListReordering();
- return 0;
- }
-
private void printInitialDisplayDensity(PrintWriter pw , int displayId) {
try {
final int initialDensity = mInterface.getInitialDisplayDensity(displayId);
@@ -1499,8 +1492,6 @@
printLetterboxHelp(pw);
printMultiWindowConfigHelp(pw);
- pw.println(" reset-freeze-recent-tasks");
- pw.println(" Resets the spatial ordering of the recent tasks list");
pw.println(" reset [-d DISPLAY_ID]");
pw.println(" Reset all override settings.");
if (!IS_USER) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6d7e297..376cad7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -99,6 +99,7 @@
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -1993,8 +1994,10 @@
creationParams.getFragmentToken(), true /* createdByOrganizer */);
// Set task fragment organizer immediately, since it might have to be notified about further
// actions.
- taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
- ownerActivity.getUid(), ownerActivity.info.processName);
+ TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
+ taskFragment.setTaskFragmentOrganizer(organizerToken,
+ ownerActivity.getUid(), ownerActivity.info.processName,
+ mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING
index eb9db70..7f7eb48 100644
--- a/services/core/jni/TEST_MAPPING
+++ b/services/core/jni/TEST_MAPPING
@@ -7,7 +7,6 @@
"name": "CtsVibratorTestCases",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index fccd1ec..0d5534b 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsDevicePolicyManagerTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index cedbfd2b..de9f771 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksInputMethodSystemServerTests",
"options": [
{"include-filter": "com.android.server.inputmethod"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -15,7 +14,6 @@
"name": "FrameworksImeTests",
"options": [
{"include-filter": "com.android.inputmethodservice"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index dca69eb..8b54d6d2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.Sensor;
@@ -158,6 +159,7 @@
.setStrictness(Strictness.LENIENT)
.spyStatic(SystemProperties.class)
.spyStatic(BatteryStatsService.class)
+ .spyStatic(ActivityManager.class)
.build();
@Rule
@@ -182,9 +184,14 @@
mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+ doAnswer((Answer<Boolean>) invocationOnMock -> false)
+ .when(ActivityManager::isLowRamDeviceStatic);
setUpSensors();
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index edaa1d5..37ee23f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.Sensor;
@@ -158,6 +159,7 @@
.setStrictness(Strictness.LENIENT)
.spyStatic(SystemProperties.class)
.spyStatic(BatteryStatsService.class)
+ .spyStatic(ActivityManager.class)
.build();
@Rule
@@ -186,9 +188,14 @@
mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+ doAnswer((Answer<Boolean>) invocationOnMock -> false)
+ .when(ActivityManager::isLowRamDeviceStatic);
setUpSensors();
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c3322ec..c63fac9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -118,7 +118,8 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 4
+ // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
+ assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -146,7 +147,8 @@
mClock.fastForward(1000);
mTestHandler.timeAdvance();
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(0.2f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 2
+ // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
+ assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -173,7 +175,8 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
+ assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
// MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING
index d73d83d..a644ea6 100644
--- a/services/tests/dreamservicetests/TEST_MAPPING
+++ b/services/tests/dreamservicetests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "DreamServiceTests",
"options": [
{"include-filter": "com.android.server.dreams"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
index 3514276..4c4e9df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
@@ -27,13 +27,12 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.Settings;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.R;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
import com.android.internal.util.SettingsWrapper;
import org.junit.Before;
@@ -48,9 +47,9 @@
@Mock
private Context mContext;
@Mock
- private Resources mResources;
- @Mock
private SettingsWrapper mSettingsWrapper;
+ @Mock
+ private FoldLockSettingAvailabilityProvider mFoldLockSettingAvailabilityProvider;
private ContentResolver mContentResolver;
private FoldSettingProvider mFoldSettingProvider;
@@ -61,17 +60,18 @@
mContentResolver =
InstrumentationRegistry.getInstrumentation().getContext().getContentResolver();
when(mContext.getContentResolver()).thenReturn(mContentResolver);
- when(mContext.getResources()).thenReturn(mResources);
setFoldLockBehaviorAvailability(true);
- mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+ mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+ mFoldLockSettingAvailabilityProvider);
}
@Test
public void foldSettingNotAvailable_returnDefaultSetting() {
setFoldLockBehaviorAvailability(false);
setFoldLockBehaviorSettingValue(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
- mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+ mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+ mFoldLockSettingAvailabilityProvider);
boolean shouldSelectiveStayAwakeOnFold =
mFoldSettingProvider.shouldSelectiveStayAwakeOnFold();
@@ -83,7 +83,8 @@
public void foldSettingNotAvailable_notReturnStayAwakeOnFoldTrue() {
setFoldLockBehaviorAvailability(false);
setFoldLockBehaviorSettingValue(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
- mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+ mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+ mFoldLockSettingAvailabilityProvider);
boolean shouldStayAwakeOnFold = mFoldSettingProvider.shouldStayAwakeOnFold();
@@ -94,7 +95,8 @@
public void foldSettingNotAvailable_notReturnSleepOnFoldTrue() {
setFoldLockBehaviorAvailability(false);
setFoldLockBehaviorSettingValue(SETTING_VALUE_SLEEP_ON_FOLD);
- mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+ mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+ mFoldLockSettingAvailabilityProvider);
boolean shouldSleepOnFold = mFoldSettingProvider.shouldSleepOnFold();
@@ -131,7 +133,7 @@
}
private void setFoldLockBehaviorAvailability(boolean isFoldLockBehaviorEnabled) {
- when(mResources.getBoolean(R.bool.config_fold_lock_behavior)).thenReturn(
+ when(mFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable()).thenReturn(
isFoldLockBehaviorEnabled);
}
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index e1eb1e4..eee68a4 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "PowerStatsTests",
"options": [
{"include-filter": "com.android.server.power.stats"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index a508718..b69b554 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -125,6 +125,8 @@
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
anyLong(), anyInt(), eq(requireConfirmation),
eq(targetUserId), any());
+
+ verify(mAuthenticationStatsCollector).authenticate(eq(targetUserId), eq(authenticated));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
new file mode 100644
index 0000000..8f77e9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.IVirtualCameraSession;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.companion.virtual.camera.VirtualCameraSession;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtual.flags.Flags;
+import android.content.ComponentName;
+import android.graphics.ImageFormat;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.server.companion.virtual.camera.IVirtualCameraService;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class VirtualCameraTest {
+
+ private static final String PKG = "com.android.virtualcamera";
+ private static final String CLS = ".VirtualCameraService";
+ public static final String CAMERA_DISPLAY_NAME = "testCamera";
+
+ private final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+ private FakeVirtualCameraService mFakeVirtualCameraService;
+ private VirtualCameraController mVirtualCameraController;
+
+ @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule =
+ new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.CREATE_VIRTUAL_DEVICE);
+
+ @Before
+ public void setUp() {
+ mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController);
+ mFakeVirtualCameraService = new FakeVirtualCameraService();
+ connectFakeService();
+ mVirtualCameraController = new VirtualCameraController(mContext);
+ }
+
+ private VirtualDeviceImpl createVirtualDevice() {
+ return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
+ }
+
+ private void connectFakeService() {
+ mContext.addMockService(
+ ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder());
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+ @Test
+ public void addVirtualCamera() {
+ VirtualDeviceImpl virtualDevice = createVirtualDevice();
+ VirtualCameraConfig config = createVirtualCameraConfig(null);
+ IVirtualCamera.Default camera = new IVirtualCamera.Default();
+ virtualDevice.registerVirtualCamera(camera);
+
+ assertThat(mFakeVirtualCameraService.mCameras).contains(camera);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+ @Test
+ public void addVirtualCamera_serviceNotReady() {
+ TestableContext context =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+ VirtualCameraController virtualCameraController = new VirtualCameraController(context);
+ mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController);
+
+ VirtualDeviceImpl virtualDevice =
+ mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
+ IVirtualCamera.Default camera = new IVirtualCamera.Default();
+ VirtualCameraConfig config = createVirtualCameraConfig(null);
+ virtualDevice.registerVirtualCamera(camera);
+ FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService();
+
+ // Only add the service after connecting the camera
+ virtualCameraController.onServiceConnected(
+ ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder());
+
+ assertThat(fakeVirtualCameraService.mCameras).contains(camera);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+ @Test
+ public void getCameraConfiguration() {
+ VirtualDeviceImpl virtualDevice = createVirtualDevice();
+ VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
+ VirtualCameraConfig config =
+ new VirtualCameraConfig.Builder()
+ .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
+ .setDisplayName(CAMERA_DISPLAY_NAME)
+ .setCallback(
+ new HandlerExecutor(new Handler(Looper.getMainLooper())),
+ () -> virtualCameraSession)
+ .build();
+
+ VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
+
+ VirtualCameraConfig returnedConfig = virtualCamera.getConfig();
+ assertThat(returnedConfig).isNotNull();
+ assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME);
+ Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs();
+ assertThat(streamConfigs).hasSize(1);
+ VirtualCameraStreamConfig streamConfig =
+ streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0];
+ assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565);
+ assertThat(streamConfig.width).isEqualTo(10);
+ assertThat(streamConfig.height).isEqualTo(10);
+
+ VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig();
+ assertThat(halConfig).isNotNull();
+ assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME);
+ assertThat(halConfig.streamConfigs).asList().hasSize(1);
+ assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565);
+ assertThat(halConfig.streamConfigs[0].width).isEqualTo(10);
+ assertThat(halConfig.streamConfigs[0].height).isEqualTo(10);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+ @Test
+ public void createCameraWithVirtualCameraInstance() {
+ VirtualDeviceImpl virtualDevice = createVirtualDevice();
+
+ VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
+ VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession);
+ VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
+
+ assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera);
+ assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class);
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA)
+ @Test
+ public void createCameraDoesNothingWhenControllerIsNull() {
+ mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null);
+ VirtualDeviceImpl virtualDevice = createVirtualDevice();
+ IVirtualCamera.Default camera = new IVirtualCamera.Default();
+ VirtualCameraConfig config = createVirtualCameraConfig(null);
+ virtualDevice.registerVirtualCamera(camera);
+
+ assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera);
+ }
+
+ @NonNull
+ private static VirtualCameraConfig createVirtualCameraConfig(
+ VirtualCameraSession virtualCameraSession) {
+ return new VirtualCameraConfig.Builder()
+ .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
+ .setDisplayName(CAMERA_DISPLAY_NAME)
+ .setCallback(
+ new HandlerExecutor(new Handler(Looper.getMainLooper())),
+ () -> virtualCameraSession)
+ .build();
+ }
+
+ private static class FakeVirtualCameraService extends IVirtualCameraService.Stub {
+
+ final Set<IVirtualCamera> mCameras = new HashSet<>();
+
+ @Override
+ public boolean registerCamera(IVirtualCamera camera) throws RemoteException {
+ mCameras.add(camera);
+ return true;
+ }
+
+ @Override
+ public void unregisterCamera(IVirtualCamera camera) throws RemoteException {
+ mCameras.remove(camera);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 263d470..61b30a0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -118,6 +118,7 @@
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
import com.android.server.input.InputManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
@@ -1841,13 +1842,25 @@
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
VirtualDeviceParams params) {
- VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, mVdms, mVirtualDeviceLog, new Binder(),
- new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
- virtualDeviceId,
- mInputController, mCameraAccessController,
- mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
- mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
+ VirtualDeviceImpl virtualDeviceImpl =
+ new VirtualDeviceImpl(
+ mContext,
+ mAssociationInfo,
+ mVdms,
+ mVirtualDeviceLog,
+ new Binder(),
+ new AttributionSource(
+ ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
+ virtualDeviceId,
+ mInputController,
+ mCameraAccessController,
+ mPendingTrampolineCallback,
+ mActivityListener,
+ mSoundEffectListener,
+ mRunningAppsChangedCallback,
+ params,
+ new DisplayManagerGlobal(mIDisplayManager),
+ new VirtualCameraController(mContext));
mVdms.addVirtualDevice(virtualDeviceImpl);
assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
assertThat(virtualDeviceImpl.getPersistentDeviceId())
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
new file mode 100644
index 0000000..af633cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import android.app.admin.DevicePolicyManager;
+import android.companion.AssociationInfo;
+import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.flags.Flags;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.net.MacAddress;
+import android.os.Binder;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/** Test rule to generate instances of {@link VirtualDeviceImpl}. */
+public class VirtualDeviceRule implements TestRule {
+
+ private static final int DEVICE_OWNER_UID = 50;
+ private static final int VIRTUAL_DEVICE_ID = 42;
+
+ private final Context mContext;
+ private InputController mInputController;
+ private CameraAccessController mCameraAccessController;
+ private AssociationInfo mAssociationInfo;
+ private VirtualDeviceManagerService mVdms;
+ private VirtualDeviceManagerInternal mLocalService;
+ private VirtualDeviceLog mVirtualDeviceLog;
+
+ // Mocks
+ @Mock private InputController.NativeWrapper mNativeWrapperMock;
+ @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock private IDisplayManager mIDisplayManager;
+ @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
+ @Mock private DevicePolicyManager mDevicePolicyManagerMock;
+ @Mock private InputManagerInternal mInputManagerInternalMock;
+ @Mock private SensorManagerInternal mSensorManagerInternalMock;
+ @Mock private IVirtualDeviceActivityListener mActivityListener;
+ @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+ @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
+
+ // Test instance suppliers
+ private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier;
+
+ /**
+ * Create a new {@link VirtualDeviceRule}
+ *
+ * @param context The context to be used with the rule.
+ */
+ public VirtualDeviceRule(@NonNull Context context) {
+ Objects.requireNonNull(context);
+ mContext = context;
+ }
+
+ /**
+ * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the
+ * supplier returns null, a new instance will be created.
+ */
+ public VirtualDeviceRule withVirtualCameraControllerSupplier(
+ Supplier<VirtualCameraController> virtualCameraControllerSupplier) {
+ mVirtualCameraControllerSupplier = virtualCameraControllerSupplier;
+ return this;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ init(new TestableContext(mContext));
+ base.evaluate();
+ }
+ };
+ }
+
+ private void init(@NonNull TestableContext context) {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
+ doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
+ LocalServices.removeServiceForTest(InputManagerInternal.class);
+ LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = "uniqueId";
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock);
+
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController =
+ new InputController(
+ mNativeWrapperMock,
+ InstrumentationRegistry.getInstrumentation()
+ .getContext()
+ .getMainThreadHandler(),
+ context.getSystemService(WindowManager.class),
+ threadVerifier);
+ mCameraAccessController =
+ new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback);
+
+ mAssociationInfo =
+ new AssociationInfo(
+ /* associationId= */ 1,
+ 0,
+ null,
+ null,
+ MacAddress.BROADCAST_ADDRESS,
+ "",
+ null,
+ null,
+ true,
+ false,
+ false,
+ 0,
+ 0,
+ -1);
+
+ mVdms = new VirtualDeviceManagerService(context);
+ mLocalService = mVdms.getLocalServiceInstance();
+ mVirtualDeviceLog = new VirtualDeviceLog(context);
+ }
+
+ /**
+ * Create a {@link VirtualDeviceImpl} with the required mocks
+ *
+ * @param params See {@link
+ * android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int,
+ * VirtualDeviceParams)}
+ */
+ public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) {
+ VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get();
+ if (Flags.virtualCamera()) {
+ if (virtualCameraController == null) {
+ virtualCameraController = new VirtualCameraController(mContext);
+ }
+ }
+
+ VirtualDeviceImpl virtualDeviceImpl =
+ new VirtualDeviceImpl(
+ mContext,
+ mAssociationInfo,
+ mVdms,
+ mVirtualDeviceLog,
+ new Binder(),
+ new AttributionSource(
+ DEVICE_OWNER_UID,
+ "com.android.virtualdevice.test",
+ "virtualdevicerule"),
+ VIRTUAL_DEVICE_ID,
+ mInputController,
+ mCameraAccessController,
+ mPendingTrampolineCallback,
+ mActivityListener,
+ mSoundEffectListener,
+ mRunningAppsChangedCallback,
+ params,
+ new DisplayManagerGlobal(mIDisplayManager),
+ virtualCameraController);
+ mVdms.addVirtualDevice(virtualDeviceImpl);
+ return virtualDeviceImpl;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 4d1e405..e9d8b2e 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-filter": "com.android.server.recoverysystem."
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 5a482fc..f221b75 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -484,6 +484,7 @@
new Callback());
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -534,6 +535,7 @@
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -555,6 +557,7 @@
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -577,6 +580,7 @@
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -599,6 +603,7 @@
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -623,6 +628,7 @@
new Callback());
verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -638,6 +644,7 @@
// Only called MAX_NUM_DENIALS times
verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+ anyInt(),
any(),
any(),
any(),
@@ -658,6 +665,7 @@
new Callback());
verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index bfa279d..2bf1385 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -212,7 +212,30 @@
mController.dispatchPendingEvents();
assertTaskFragmentParentInfoChangedTransaction(mTask);
- assertTaskFragmentAppearedTransaction();
+ assertTaskFragmentAppearedTransaction(false /* hasSurfaceControl */);
+ }
+
+ @Test
+ public void testOnTaskFragmentAppeared_systemOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+ // No-op when the TaskFragment is not attached.
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer, never()).onTransactionReady(any());
+
+ // Send callback when the TaskFragment is attached.
+ setupMockParent(mTaskFragment, mTask);
+
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ assertTaskFragmentParentInfoChangedTransaction(mTask);
+
+ // System organizer should receive the SurfaceControl
+ assertTaskFragmentAppearedTransaction(true /* hasSurfaceControl */);
}
@Test
@@ -1664,7 +1687,7 @@
}
/** Asserts that there will be a transaction for TaskFragment appeared. */
- private void assertTaskFragmentAppearedTransaction() {
+ private void assertTaskFragmentAppearedTransaction(boolean hasSurfaceControl) {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
@@ -1675,6 +1698,11 @@
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
+ if (hasSurfaceControl) {
+ assertNotNull(change.getTaskFragmentSurfaceControl());
+ } else {
+ assertNull(change.getTaskFragmentSurfaceControl());
+ }
}
/** Asserts that there will be a transaction for TaskFragment info changed. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6a9bb6c..5205bb0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -27,8 +27,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -124,6 +128,17 @@
}
@Test
+ public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() {
+ clearInvocations(mTransaction);
+ mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true;
+
+ mTaskFragment.updateOrganizedTaskFragmentSurface();
+
+ verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat());
+ verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt());
+ }
+
+ @Test
public void testShouldStartChangeTransition_relativePositionChange() {
final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 98cc1da..138e575 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2403,10 +2403,6 @@
}
@Override
- public void onHandleUserStop(Intent intent, int userHandle) {
- }
-
- @Override
public void onPackageModified(@NonNull String pkgName) {
// If the package modified is not in the current user, then don't bother making
// any changes as we are going to do any initialization needed when we switch users.
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 29a952a..250c3a5 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -35,6 +35,8 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
import java.util.HashMap;
import java.util.HashSet;
@@ -46,7 +48,8 @@
private static final String LOG_TAG = "TelephonyPermissions";
private static final boolean DBG = false;
-
+ /** Feature flags */
+ private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl();
/**
* Whether to disable the new device identifier access restrictions.
*/
@@ -854,7 +857,8 @@
public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
@NonNull UserHandle callerUserHandle, @NonNull String destAddr) {
// Skip subscription-user association check for emergency numbers
- TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
final long token = Binder.clearCallingIdentity();
try {
if (tm != null && tm.isEmergencyNumber(destAddr)) {
@@ -876,16 +880,19 @@
* @param context Context
* @param subId subscription ID
* @param callerUserHandle caller user handle
- * @return false if user is not associated with the subscription.
+ * @return false if user is not associated with the subscription, or no record found of this
+ * subscription.
*/
public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
@NonNull UserHandle callerUserHandle) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- // No subscription on device, return true.
+ if (!sFeatureFlag.rejectBadSubIdInteraction()
+ && !SubscriptionManager.isValidSubscriptionId(subId)) {
+ // Return true for invalid sub Id.
return true;
}
- SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+ SubscriptionManager subManager = (SubscriptionManager) context.getSystemService(
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE);
final long token = Binder.clearCallingIdentity();
try {
if ((subManager != null) &&
@@ -894,8 +901,11 @@
Log.e(LOG_TAG, "User[User ID:" + callerUserHandle.getIdentifier()
+ "] is not associated with Subscription ID:" + subId);
return false;
-
}
+ } catch (IllegalArgumentException e) {
+ // Found no record of this sub Id.
+ Log.e(LOG_TAG, "Subscription[Subscription ID:" + subId + "] has no records on device");
+ return !sFeatureFlag.rejectBadSubIdInteraction();
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 9a8c965..aed8fb8 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -274,6 +274,10 @@
SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
+ if (!subscriptionManager.isActiveSubscriptionId(subId)) {
+ Log.e(LOG_TAG, "Tried to send message with an inactive subscription " + subId);
+ return;
+ }
UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
UserManager um = context.getSystemService(UserManager.class);
@@ -319,4 +323,4 @@
return false;
}
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 36a8fc1..a5e0638 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,6 +53,7 @@
import android.telephony.ims.feature.RcsFeature;
import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.internal.telephony.flags.Flags;
import com.android.telephony.Rlog;
import java.util.List;
@@ -9436,22 +9438,9 @@
* </carrier_config>
* }</pre>
* <p>
- * If this carrier config is not present, the device overlay config
- * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
- * is present, the supported services associated with the PLMNs listed in the carrier config
- * will override that of the device overlay config. The supported satellite services will be
- * identified as follows:
- * <ul>
- * <li>For each PLMN that exists only in the carrier provided satellite services, use the
- * carrier provided services as the supported services.</li>
- * <li>For each PLMN that is present only in the device provided satellite services, use the
- * device provided services as the supported services.</li>
- * <li>For each PLMN that is present in both the carrier provided and device provided satellite
- * services, use the carrier provided services as the supported services.</li>
- * </ul>
- * <p>
* This config is empty by default.
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
"carrier_supported_satellite_services_per_provider_bundle";
@@ -9462,9 +9451,8 @@
* satellite provider and the carrier before enabling this flag.
*
* The default value is false.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
"satellite_attach_supported_bool";
@@ -10430,8 +10418,12 @@
sDefaults.putAll(Iwlan.getDefaults());
sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
- sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
- new int[] {4 /* BUSY */});
+ if (Flags.doNotOverridePreciseLabel()) {
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[]{});
+ } else {
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ new int[]{4 /* BUSY */});
+ }
sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000);
sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 29149b9..fa5fd87 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4348,7 +4348,7 @@
* {code true} if there are no subscriptions on device
* else {@code false} if subscription is not associated with user.
*
- * @throws IllegalArgumentException if subscription is invalid.
+ * @throws IllegalArgumentException if subscription doesn't exist.
* @throws SecurityException if the caller doesn't have permissions required.
*
* @hide
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index a4527f12..8dc2de8 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -85,29 +85,11 @@
@Nullable private final Context mContext;
/**
- * Create a new SatelliteManager object pinned to the given subscription ID.
- * This is needed only to handle carrier specific satellite features.
- * For eg: requestSatelliteAttachEnabledForCarrier and
- * requestIsSatelliteAttachEnabledForCarrier
- *
- * @return a SatelliteManager that uses the given subId for all satellite activities.
- * @throws IllegalArgumentException if the subscription is invalid.
- * @hide
- */
- public SatelliteManager createForSubscriptionId(int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- throw new IllegalArgumentException("Invalid subscription ID");
- }
- return new SatelliteManager(mContext, subId);
- }
-
- /**
* Create an instance of the SatelliteManager.
*
* @param context The context the SatelliteManager belongs to.
* @hide
*/
-
public SatelliteManager(@Nullable Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
@@ -846,8 +828,8 @@
/**
* Satellite communication restricted by geolocation. This can be
* triggered based upon geofence input provided by carrier to enable or disable satellite.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
/** @hide */
@@ -1643,26 +1625,28 @@
* {@code true}.</li>
* </ul>
*
+ * @param subId The subscription ID of the carrier.
* @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(executor);
Objects.requireNonNull(resultListener);
if (enableSatellite) {
- removeSatelliteAttachRestrictionForCarrier(
+ removeSatelliteAttachRestrictionForCarrier(subId,
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
} else {
- addSatelliteAttachRestrictionForCarrier(
+ addSatelliteAttachRestrictionForCarrier(subId,
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
}
}
@@ -1671,6 +1655,7 @@
* Request to get whether the carrier supported satellite plmn scan and attach by modem is
* enabled by user.
*
+ * @param subId The subscription ID of the carrier.
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
* If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -1681,16 +1666,17 @@
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestIsSatelliteAttachEnabledForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void requestIsSatelliteAttachEnabledForCarrier(int subId,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+ Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId);
executor.execute(() -> callback.onResult(
!restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
}
@@ -1699,19 +1685,25 @@
* Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
* by modem.
*
+ * @param subId The subscription ID of the carrier.
* @param reason Reason for disallowing satellite communication.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void addSatelliteAttachRestrictionForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void addSatelliteAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -1722,7 +1714,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1736,19 +1728,25 @@
* Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
* by modem.
*
+ * @param subId The subscription ID of the carrier.
* @param reason Reason for disallowing satellite communication.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void removeSatelliteAttachRestrictionForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void removeSatelliteAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -1759,7 +1757,7 @@
() -> resultListener.accept(result)));
}
};
- telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1773,34 +1771,40 @@
* Get reasons for disallowing satellite attach, as requested by
* {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
*
+ * @param subId The subscription ID of the carrier.
* @return Set of reasons for disallowing satellite communication.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteCommunicationRestrictionReason
- public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
int[] receivedArray =
- telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+ telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId);
if (receivedArray.length == 0) {
- logd("received set is empty, create empty set");
+ logd("receivedArray is empty, create empty set");
return new HashSet<>();
} else {
return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
}
} else {
- throw new IllegalStateException("telephony service is null.");
+ throw new IllegalStateException("Telephony service is null.");
}
} catch (RemoteException ex) {
loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
- return null;
+ return new HashSet<>();
}
private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 02661de..0fcd0d6 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -392,7 +392,11 @@
*
* @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
* this information to determine the relevant carrier.
- * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+ * supported by user subscription.
+ * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+ * PLMNs that are not supported by the carrier and make sure not to
+ * attach to them.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -404,8 +408,8 @@
* SatelliteError:RADIO_NOT_AVAILABLE
* SatelliteError:REQUEST_NOT_SUPPORTED
*/
- void setSatellitePlmn(int simSlot, in List<String> plmnList,
- in IIntegerConsumer resultCallback);
+ void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
+ in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
/**
* Enable or disable satellite in the cellular modem associated with a carrier.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 6451daf..a9c09c9 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -214,12 +214,12 @@
}
@Override
- public void setSatellitePlmn(int simSlot, List<String> plmnList,
- IIntegerConsumer errorCallback)
+ public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
+ List<String> devicePlmnList, IIntegerConsumer errorCallback)
throws RemoteException {
executeMethodAsync(
- () -> SatelliteImplBase.this
- .setSatellitePlmn(simSlot, plmnList, errorCallback),
+ () -> SatelliteImplBase.this.setSatellitePlmn(
+ simSlot, carrierPlmnList, devicePlmnList, errorCallback),
"setSatellitePlmn");
}
@@ -655,13 +655,15 @@
* SIM profile. Acquisition of satellite based system is lower priority to terrestrial
* networks. UE shall make all attempts to acquire terrestrial service prior to camping on
* satellite LTE service.
- * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
- * and return an error otherwise.
*
* @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
* applied. The modem will use this information to determine the relevant carrier.
* @param errorCallback The callback to receive the error code result of the operation.
- * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+ * supported by user subscription.
+ * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+ * PLMNs that are not supported by the carrier and make sure not to
+ * attach to them.
*
* Valid error codes returned:
* SatelliteError:NONE
@@ -672,7 +674,8 @@
* SatelliteError:RADIO_NOT_AVAILABLE
* SatelliteError:REQUEST_NOT_SUPPORTED
*/
- public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+ public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
+ @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
diff --git a/tests/utils/testutils/TEST_MAPPING b/tests/utils/testutils/TEST_MAPPING
index 6468d8c..52fd5a8 100644
--- a/tests/utils/testutils/TEST_MAPPING
+++ b/tests/utils/testutils/TEST_MAPPING
@@ -7,9 +7,6 @@
"exclude-annotation": "androidx.test.filters.LargeTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
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 95cbff9..d176b5e 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
@@ -69,7 +69,8 @@
String methodDescriptor,
String callbackMethod
) {
- callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor);
+ callStaticMethodByName(callbackMethod, "method call hook", methodClass,
+ methodName, methodDescriptor);
}
/**
@@ -167,31 +168,38 @@
logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
+ " calling hook " + callbackMethod);
- callStaticMethodByName(callbackMethod, loadedClass);
+ callStaticMethodByName(callbackMethod, "class load hook", loadedClass);
}
- private static void callStaticMethodByName(String classAndMethodName, Object... args) {
+ private static void callStaticMethodByName(String classAndMethodName,
+ String description, Object... args) {
// Forward the call to callbackMethod.
final int lastPeriod = classAndMethodName.lastIndexOf(".");
- final String className = classAndMethodName.substring(0, lastPeriod);
- final String methodName = classAndMethodName.substring(lastPeriod + 1);
- if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
+ if ((lastPeriod) < 0 || (lastPeriod == classAndMethodName.length() - 1)) {
throw new HostTestException(String.format(
- "Unable to find class load hook: malformed method name \"%s\"",
+ "Unable to find %s: malformed method name \"%s\"",
+ description,
classAndMethodName));
}
+ final String className = classAndMethodName.substring(0, lastPeriod);
+ final String methodName = classAndMethodName.substring(lastPeriod + 1);
+
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Class %s not found", className), e);
+ "Unable to find %s: Class %s not found",
+ description,
+ className), e);
}
if (!Modifier.isPublic(clazz.getModifiers())) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Class %s must be public", className));
+ "Unable to find %s: Class %s must be public",
+ description,
+ className));
}
Class<?>[] argTypes = new Class[args.length];
@@ -204,25 +212,23 @@
method = clazz.getMethod(methodName, argTypes);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to find class load hook: class %s doesn't have method %s"
+ "Unable to find %s: class %s doesn't have method %s"
+ " (method must take exactly one parameter of type Class,"
+ " and public static)",
- className,
- methodName), e);
+ description, className, methodName), e);
}
if (!(Modifier.isPublic(method.getModifiers())
&& Modifier.isStatic(method.getModifiers()))) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Method %s in class %s must be public static",
- methodName, className));
+ "Unable to find %s: Method %s in class %s must be public static",
+ description, methodName, className));
}
try {
method.invoke(null, args);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to invoke class load hook %s.%s",
- className,
- methodName), e);
+ "Unable to invoke %s %s.%s",
+ description, className, methodName), e);
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 7d7852a..f75062b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -32,6 +32,10 @@
return uncommented.trim()
}
+/**
+ * Concatenate list [a] and [b] and return it. As an optimization, it returns an input
+ * [List] as-is if the other [List] is empty, so do not modify input [List]'s.
+ */
fun <T> addLists(a: List<T>, b: List<T>): List<T> {
if (a.isEmpty()) {
return b
@@ -42,6 +46,10 @@
return a + b
}
+/**
+ * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a].
+ * (because the method may return [a] as-is, do not modify it after passing it.)
+ */
fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
if (b == null) {
return a