summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManagerInternal.java10
-rw-r--r--core/java/android/content/Intent.java9
-rw-r--r--core/java/android/os/INetworkManagementService.aidl39
-rw-r--r--core/tests/coretests/AndroidManifest.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt42
-rw-r--r--location/java/android/location/GnssMeasurement.java47
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt385
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt92
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt126
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt145
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt172
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/keyguard/Android.bp16
-rw-r--r--packages/SystemUI/shared/keyguard/AndroidManifest.xml20
-rw-r--r--packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java239
-rw-r--r--packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java (renamed from packages/SystemUI/src/com/android/keyguard/PinShapeInput.java)8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java453
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java215
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt121
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt22
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java20
-rw-r--r--services/core/java/com/android/server/net/NetworkManagementService.java36
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java5
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java14
-rw-r--r--services/core/java/com/android/server/pm/DumpHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java17
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java12
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java1
-rw-r--r--services/core/java/com/android/server/pm/MovePackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageFreezer.java23
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java19
-rw-r--r--services/core/java/com/android/server/pm/ShortcutUser.java4
-rw-r--r--services/core/java/com/android/server/pm/StorageEventHelper.java3
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt9
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java137
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java2
89 files changed, 3689 insertions, 644 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 021f932c562c..32c40df32585 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -35,6 +35,7 @@ import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -1224,4 +1225,13 @@ public abstract class ActivityManagerInternal {
*/
@NonNull
public abstract StatsEvent getCachedAppsHighWatermarkStats(int atomTag, boolean resetAfterPull);
+
+ /**
+ * Internal method for clearing app data, with the extra param that is used to indicate restore.
+ * Used by Backup service during restore operation.
+ *
+ * @hide
+ */
+ public abstract boolean clearApplicationUserData(String packageName, boolean keepState,
+ boolean isRestore, IPackageDataObserver observer, int userId);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index afeb3d295a7c..31f6418ac3d0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6705,6 +6705,15 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_VISIBILITY_ALLOW_LIST =
"android.intent.extra.VISIBILITY_ALLOW_LIST";
+ /**
+ * A boolean extra used with {@link #ACTION_PACKAGE_DATA_CLEARED} which indicates if the intent
+ * is broadcast as part of a restore operation.
+ *
+ * @hide
+ */
+ public static final String EXTRA_IS_RESTORE =
+ "android.intent.extra.IS_RESTORE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index ed14652c0f0a..1a3dcee427d8 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -126,50 +126,65 @@ interface INetworkManagementService
/**
* Returns true if IP forwarding is enabled
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}")
boolean getIpForwardingEnabled();
/**
* Enables/Disables IP Forwarding
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}. See also "
+ + "{@code INetd#ipfwdEnableForwarding(String)}.")
void setIpForwardingEnabled(boolean enabled);
/**
* Start tethering services with the specified dhcp server range
* arg is a set of start end pairs defining the ranges.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#startTethering}")
void startTethering(in String[] dhcpRanges);
/**
* Stop currently running tethering services
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}")
void stopTethering();
/**
* Returns true if tethering services are started
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Generally track your own tethering requests. "
+ + "See also {@code android.net.INetd#tetherIsEnabled()}")
boolean isTetheringStarted();
/**
* Tethers the specified interface
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}. See also "
+ + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.")
void tetherInterface(String iface);
/**
* Untethers the specified interface
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, disable "
+ + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. "
+ + "See also {@code NetdUtils#untetherInterface}.")
void untetherInterface(String iface);
/**
* Returns a list of currently tethered interfaces
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}")
String[] listTetheredInterfaces();
/**
@@ -177,13 +192,17 @@ interface INetworkManagementService
* The address and netmask of the external interface is used for
* the NAT'ed network.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}.")
void enableNat(String internalInterface, String externalInterface);
/**
* Disables Network Address Translation between two interfaces.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, disable tethering with "
+ + "{@code android.net.TetheringManager#stopTethering(int)}.")
void disableNat(String internalInterface, String externalInterface);
/**
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a358c4f6f7e9..20d8d91761e7 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,8 +86,8 @@
<uses-permission android:name="android.permission.TEST_GRANTED" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" android:maxSdkVersion="34"/>
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" android:maxSdkVersion="34"/>
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index ec09827fa4d1..6dabb3bf6f9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,3 +1,4 @@
# WM shell sub-module pip owner
hwwang@google.com
mateuszc@google.com
+gabiyev@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 000000000000..a5c512267508
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 000000000000..092fb6720e57
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 000000000000..8cb25fe531b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 000000000000..fa1be63296e0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..aa35237b615f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..e195360cdc36
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..c1b3aade11cd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..c6e2e854ab89
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 000000000000..5f771c7545c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 000000000000..729a401fe34c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 000000000000..6e4cf9f55cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 000000000000..cc2870213e8d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 000000000000..736604f02377
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 000000000000..8df8dfaab071
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 000000000000..378f055ef830
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 000000000000..b33d26288d33
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 000000000000..b1d3858b9dbb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 000000000000..6d824c74c1fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 000000000000..f1d3d0cf2716
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 000000000000..a867bac8c0eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 000000000000..76247ba10037
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 000000000000..e179da81e4db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..20f554f7d154
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..f7776ee3244a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 000000000000..00f607343b3b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 000000000000..b3340e77dbfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -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 com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 000000000000..3da61e5e310c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 000000000000..627ae1843314
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 000000000000..f4e72987becd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 000000000000..f38b2e8e12dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index b0769abce295..e28ad6757967 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1525,50 +1525,61 @@ public final class GnssMeasurement implements Parcelable {
/**
* Gets the GNSS measurement's code type.
*
- * <p>Similar to the Attribute field described in RINEX 3.03, e.g., in Tables 4-10, and Table
- * A2 at the RINEX 3.03 Update 1 Document.
+ * <p>Similar to the Attribute field described in RINEX 4.00, e.g., in Tables 9-16 (see
+ * https://igs.org/wg/rinex/#documents-formats).
*
- * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A, IRNSS SA.
+ * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS, GLONASS G1a L1OCd,
+ * GLONASS G2a L2CSI.
*
- * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B, IRNSS SB.
+ * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D), IRNSS SB RS (D), GLONASS G1a
+ * L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
*
- * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
- * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C.
+ * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
+ * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P), IRNSS SC RS (P).
*
- * <p>Returns "D" for BDS B1C D.
+ * <p>Returns "D" for GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)), QZSS L5S(I), BDS B1C Data,
+ * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+ *
+ * <p>Returns “E” for QZSS L1 C/B, QZSS L6E.
*
* <p>Returns "I" for GPS L5 I, GLONASS G3 I, GALILEO E5a I, GALILEO E5b I, GALILEO E5a+b I,
* SBAS L5 I, QZSS L5 I, BDS B1 I, BDS B2 I, BDS B3 I.
*
- * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), LEX(6) L.
+ * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), QZSS L6P, BDS
+ * B1a Pilot.
*
* <p>Returns "M" for GPS L1M, GPS L2M.
*
* <p>Returns "N" for GPS L1 codeless, GPS L2 codeless.
*
- * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C P.
+ * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C Pilot, BDS B2a Pilot,
+ * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q).
*
* <p>Returns "Q" for GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q,
* SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q.
*
- * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), LEX(6) S.
+ * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), QZSS L6D, BDS B1a
+ * Data.
*
* <p>Returns "W" for GPS L1 Z-tracking, GPS L2 Z-tracking.
*
- * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G3 (I+Q), GALILEO
- * E1 (B+C), GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b(I+Q), GALILEO E6 (B+C), SBAS
- * L5 (I+Q), QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q), LEX(6) (S+L), BDS B1 (I+Q), BDS
- * B1C (D+P), BDS B2 (I+Q), BDS B3 (I+Q), IRNSS L5 (B+C).
+ * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G1a L1OCd+L1OCp,
+ * GLONASS G2a L2CSI+L2OCp, GLONASS G3 (I+Q), GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO
+ * E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C
+ * (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q), BDS B1C Data+Pilot, BDS B2a Data+Pilot,
+ * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
*
* <p>Returns "Y" for GPS L1Y, GPS L2Y.
*
- * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1-SAIF.
+ * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1S/L1-SAIF, QZSS L5S (I+Q),
+ * QZSS L6(D+E), BDS B1A Data+Pilot, BDS B2b Data+Pilot, BDS B3a Data+Pilot.
*
* <p>Returns "UNKNOWN" if the GNSS Measurement's code type is unknown.
*
- * <p>This is used to specify the observation descriptor defined in GNSS Observation Data File
- * Header Section Description in the RINEX standard (Version 3.XX), in cases where the code type
- * does not align with the above listed values. For example, if a code type "G" is added, this
+ * <p>The code type is used to specify the observation descriptor defined in GNSS Observation
+ * Data File Header Section Description in the RINEX standard (Version 4.00). In cases where
+ * the code type does not align with the above listed values, the code type from the most
+ * recent version of RINEX should be used. For example, if a code type "G" is added, this
* string shall be set to "G".
*/
@NonNull
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
index d9a45cd663c2..2069ebd32b81 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -19,18 +19,25 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
+import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -70,23 +77,38 @@ internal fun Modifier.swipeToScene(
// the same as SwipeableV2Defaults.PositionalThreshold.
val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
- return draggable(
- orientation = orientation,
- enabled = enabled,
- startDragImmediately = startDragImmediately,
- onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
- state =
- rememberDraggableState { delta -> onDrag(layoutImpl, transition, orientation, delta) },
- onDragStopped = { velocity ->
- onDragStopped(
- layoutImpl,
- transition,
- velocity,
- velocityThreshold,
- positionalThreshold,
- )
- },
- )
+ val draggableState = rememberDraggableState { delta ->
+ onDrag(layoutImpl, transition, orientation, delta)
+ }
+
+ return nestedScroll(
+ connection =
+ rememberSwipeToSceneNestedScrollConnection(
+ orientation = orientation,
+ coroutineScope = rememberCoroutineScope(),
+ draggableState = draggableState,
+ transition = transition,
+ layoutImpl = layoutImpl,
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold
+ ),
+ )
+ .draggable(
+ state = draggableState,
+ orientation = orientation,
+ enabled = enabled,
+ startDragImmediately = startDragImmediately,
+ onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
+ onDragStopped = { velocity ->
+ onDragStopped(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocity,
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold,
+ )
+ },
+ )
}
private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
@@ -235,35 +257,18 @@ private fun onDrag(
// twice in a row to accelerate the transition and go from A => B then B => C really fast.
maybeHandleAcceleratedSwipe(transition, orientation)
- val fromScene = transition._fromScene
- val upOrLeft = fromScene.upOrLeft(orientation)
- val downOrRight = fromScene.downOrRight(orientation)
val offset = transition.dragOffset
+ val fromScene = transition._fromScene
// Compute the target scene depending on the current offset.
- val targetSceneKey: SceneKey
- val signedDistance: Float
- when {
- offset < 0f && upOrLeft != null -> {
- targetSceneKey = upOrLeft
- signedDistance = -transition.absoluteDistance
- }
- offset > 0f && downOrRight != null -> {
- targetSceneKey = downOrRight
- signedDistance = transition.absoluteDistance
- }
- else -> {
- targetSceneKey = fromScene.key
- signedDistance = 0f
- }
- }
+ val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
- if (transition._toScene.key != targetSceneKey) {
- transition._toScene = layoutImpl.scenes.getValue(targetSceneKey)
+ if (transition._toScene.key != target.sceneKey) {
+ transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
}
- if (transition._distance != signedDistance) {
- transition._distance = signedDistance
+ if (transition._distance != target.distance) {
+ transition._distance = target.distance
}
}
@@ -299,12 +304,55 @@ private fun maybeHandleAcceleratedSwipe(
// using fromScene and dragOffset.
}
+private data class TargetScene(
+ val sceneKey: SceneKey,
+ val distance: Float,
+)
+
+private fun Scene.findTargetSceneAndDistance(
+ orientation: Orientation,
+ directionOffset: Float,
+ layoutImpl: SceneTransitionLayoutImpl,
+): TargetScene {
+ val maxDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
+
+ val upOrLeft = upOrLeft(orientation)
+ val downOrRight = downOrRight(orientation)
+
+ // Compute the target scene depending on the current offset.
+ return when {
+ directionOffset < 0f && upOrLeft != null -> {
+ TargetScene(
+ sceneKey = upOrLeft,
+ distance = -maxDistance,
+ )
+ }
+ directionOffset > 0f && downOrRight != null -> {
+ TargetScene(
+ sceneKey = downOrRight,
+ distance = maxDistance,
+ )
+ }
+ else -> {
+ TargetScene(
+ sceneKey = key,
+ distance = 0f,
+ )
+ }
+ }
+}
+
private fun CoroutineScope.onDragStopped(
layoutImpl: SceneTransitionLayoutImpl,
transition: SwipeTransition,
velocity: Float,
velocityThreshold: Float,
positionalThreshold: Float,
+ canChangeScene: Boolean = true,
) {
// The state was changed since the drag started; don't do anything.
if (layoutImpl.state.transitionState != transition) {
@@ -323,14 +371,15 @@ private fun CoroutineScope.onDragStopped(
val offset = transition.dragOffset
val distance = transition.distance
if (
- shouldCommitSwipe(
- offset,
- distance,
- velocity,
- velocityThreshold,
- positionalThreshold,
- wasCommitted = transition._currentScene == transition._toScene,
- )
+ canChangeScene &&
+ shouldCommitSwipe(
+ offset,
+ distance,
+ velocity,
+ velocityThreshold,
+ positionalThreshold,
+ wasCommitted = transition._currentScene == transition._toScene,
+ )
) {
targetOffset = distance
targetScene = transition._toScene
@@ -348,31 +397,13 @@ private fun CoroutineScope.onDragStopped(
layoutImpl.onChangeScene(targetScene.key)
}
- // Animate the offset.
- transition.offsetAnimationJob = launch {
- transition.offsetAnimatable.snapTo(offset)
- transition.isAnimatingOffset = true
-
- transition.offsetAnimatable.animateTo(
- targetOffset,
- // TODO(b/290184746): Make this spring spec configurable.
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = OffsetVisibilityThreshold
- ),
- initialVelocity = velocity,
- )
-
- // Now that the animation is done, the state should be idle. Note that if the state was
- // changed since this animation started, some external code changed it and we shouldn't do
- // anything here. Note also that this job will be cancelled in the case where the user
- // intercepts this swipe.
- if (layoutImpl.state.transitionState == transition) {
- layoutImpl.state.transitionState = TransitionState.Idle(targetScene.key)
- }
-
- transition.offsetAnimationJob = null
- }
+ animateOffset(
+ transition = transition,
+ layoutImpl = layoutImpl,
+ initialVelocity = velocity,
+ targetOffset = targetOffset,
+ targetScene = targetScene.key
+ )
}
/**
@@ -412,8 +443,216 @@ private fun shouldCommitSwipe(
}
}
+private fun CoroutineScope.animateOffset(
+ transition: SwipeTransition,
+ layoutImpl: SceneTransitionLayoutImpl,
+ initialVelocity: Float,
+ targetOffset: Float,
+ targetScene: SceneKey,
+) {
+ transition.offsetAnimationJob = launch {
+ if (!transition.isAnimatingOffset) {
+ transition.offsetAnimatable.snapTo(transition.dragOffset)
+ }
+ transition.isAnimatingOffset = true
+
+ transition.offsetAnimatable.animateTo(
+ targetOffset,
+ // TODO(b/290184746): Make this spring spec configurable.
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = OffsetVisibilityThreshold
+ ),
+ initialVelocity = initialVelocity,
+ )
+
+ // Now that the animation is done, the state should be idle. Note that if the state was
+ // changed since this animation started, some external code changed it and we shouldn't do
+ // anything here. Note also that this job will be cancelled in the case where the user
+ // intercepts this swipe.
+ if (layoutImpl.state.transitionState == transition) {
+ layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+ }
+
+ transition.offsetAnimationJob = null
+ }
+}
+
+private fun CoroutineScope.animateOverscroll(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: SwipeTransition,
+ velocity: Velocity,
+ orientation: Orientation,
+): Velocity {
+ val velocityAmount =
+ when (orientation) {
+ Orientation.Vertical -> velocity.y
+ Orientation.Horizontal -> velocity.x
+ }
+
+ if (velocityAmount == 0f) {
+ // There is no remaining velocity
+ return Velocity.Zero
+ }
+
+ val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
+ val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+
+ if (!isValidTarget || layoutImpl.state.transitionState == transition) {
+ // We have not found a valid target or we are already in a transition
+ return Velocity.Zero
+ }
+
+ transition._currentScene = fromScene
+ transition._fromScene = fromScene
+ transition._toScene = layoutImpl.scene(target.sceneKey)
+ transition._distance = target.distance
+ transition.absoluteDistance = target.distance.absoluteValue
+ transition.dragOffset = 0f
+ transition.isAnimatingOffset = false
+ transition.offsetAnimationJob = null
+
+ layoutImpl.state.transitionState = transition
+
+ animateOffset(
+ transition = transition,
+ layoutImpl = layoutImpl,
+ initialVelocity = velocityAmount,
+ targetOffset = 0f,
+ targetScene = fromScene.key
+ )
+
+ // The animateOffset animation consumes any remaining velocity.
+ return velocity
+}
+
/**
* The number of pixels below which there won't be a visible difference in the transition and from
* which the animation can stop.
*/
private const val OffsetVisibilityThreshold = 0.5f
+
+@Composable
+private fun rememberSwipeToSceneNestedScrollConnection(
+ orientation: Orientation,
+ coroutineScope: CoroutineScope,
+ draggableState: DraggableState,
+ transition: SwipeTransition,
+ layoutImpl: SceneTransitionLayoutImpl,
+ velocityThreshold: Float,
+ positionalThreshold: Float,
+): PriorityPostNestedScrollConnection {
+ val density = LocalDensity.current
+ val scrollConnection =
+ remember(
+ orientation,
+ coroutineScope,
+ draggableState,
+ transition,
+ layoutImpl,
+ velocityThreshold,
+ positionalThreshold,
+ density,
+ ) {
+ fun Offset.toAmount() =
+ when (orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
+
+ fun Velocity.toAmount() =
+ when (orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
+
+ fun Float.toOffset() =
+ when (orientation) {
+ Orientation.Horizontal -> Offset(x = this, y = 0f)
+ Orientation.Vertical -> Offset(x = 0f, y = this)
+ }
+
+ // The next potential scene is calculated during the canStart
+ var nextScene: SceneKey? = null
+
+ // This is the scene on which we will have priority during the scroll gesture.
+ var priorityScene: SceneKey? = null
+
+ // If we performed a long gesture before entering priority mode, we would have to avoid
+ // moving on to the next scene.
+ var gestureStartedOnNestedChild = false
+
+ PriorityPostNestedScrollConnection(
+ canStart = { offsetAvailable, offsetBeforeStart ->
+ val amount = offsetAvailable.toAmount()
+ if (amount == 0f) return@PriorityPostNestedScrollConnection false
+
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+ val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ nextScene =
+ when {
+ amount < 0f -> fromScene.upOrLeft(orientation)
+ amount > 0f -> fromScene.downOrRight(orientation)
+ else -> null
+ }
+
+ nextScene != null
+ },
+ canContinueScroll = { priorityScene == transition._toScene.key },
+ onStart = {
+ priorityScene = nextScene
+ onDragStarted(layoutImpl, transition, orientation)
+ },
+ onScroll = { offsetAvailable ->
+ val amount = offsetAvailable.toAmount()
+
+ // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
+ // is initiated in a nested child.
+
+ // Appends a new coroutine to attempt to drag by [amount] px. In this case we
+ // are assuming that the [coroutineScope] is tied to the main thread and that
+ // calls to [launch] are therefore queued.
+ coroutineScope.launch { draggableState.drag { dragBy(amount) } }
+
+ amount.toOffset()
+ },
+ onStop = { velocityAvailable ->
+ priorityScene = null
+
+ coroutineScope.onDragStopped(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocityAvailable.toAmount(),
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold,
+ canChangeScene = !gestureStartedOnNestedChild
+ )
+
+ // The onDragStopped animation consumes any remaining velocity.
+ velocityAvailable
+ },
+ onPostFling = { velocityAvailable ->
+ // If there is any velocity left, we can try running an overscroll animation
+ // between scenes.
+ coroutineScope.animateOverscroll(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocityAvailable,
+ orientation = orientation
+ )
+ },
+ )
+ }
+ DisposableEffect(scrollConnection) {
+ onDispose {
+ coroutineScope.launch {
+ // This should ensure that the draggableState is in a consistent state and that it
+ // does not cause any unexpected behavior.
+ scrollConnection.reset()
+ }
+ }
+ }
+ return scrollConnection
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
new file mode 100644
index 000000000000..cea8d9a65b43
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [height]** back to the [minHeight] and then allows
+ * scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ * then resets the [height] to [maxHeight].
+ *
+ * This behavior is useful for implementing a
+ * [Large top app bar](https://m3.material.io/components/top-app-bar/specs) effect or something
+ * similar.
+ *
+ * @sample com.android.compose.animation.scene.demo.Shade
+ */
+class LargeTopAppBarNestedScrollConnection(
+ private val height: () -> Float,
+ private val onChangeHeight: (Float) -> Unit,
+ private val minHeight: Float,
+ private val maxHeight: Float,
+) : NestedScrollConnection {
+
+ constructor(
+ height: () -> Float,
+ onHeightChanged: (Float) -> Unit,
+ heightRange: ClosedFloatingPointRange<Float>,
+ ) : this(
+ height = height,
+ onChangeHeight = onHeightChanged,
+ minHeight = heightRange.start,
+ maxHeight = heightRange.endInclusive,
+ )
+
+ /**
+ * When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will expand.
+ * Then, you can then scroll down the content.
+ */
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val y = available.y
+ val currentHeight = height()
+ if (y >= 0 || currentHeight <= minHeight) {
+ return Offset.Zero
+ }
+
+ val amountLeft = minHeight - currentHeight
+ val amountConsumed = y.coerceAtLeast(amountLeft)
+ onChangeHeight(currentHeight + amountConsumed)
+ return Offset(0f, amountConsumed)
+ }
+
+ /**
+ * When swiping down, the content will scroll up until it reaches the top. Then, the
+ * LargeTopAppBar will expand until it reaches its [maxHeight].
+ */
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ val y = available.y
+ val currentHeight = height()
+ if (y <= 0 || currentHeight >= maxHeight) {
+ return Offset.Zero
+ }
+
+ val amountLeft = maxHeight - currentHeight
+ val amountConsumed = y.coerceAtMost(amountLeft)
+ onChangeHeight(currentHeight + amountConsumed)
+ return Offset(0f, amountConsumed)
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
new file mode 100644
index 000000000000..793a9a59405a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
+ * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
+ * until [canContinueScroll] allows it.
+ *
+ * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
+ * after [onStart].
+ *
+ * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ */
+class PriorityPostNestedScrollConnection(
+ private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canContinueScroll: () -> Boolean,
+ private val onStart: () -> Unit,
+ private val onScroll: (offsetAvailable: Offset) -> Offset,
+ private val onStop: (velocityAvailable: Velocity) -> Velocity,
+ private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
+) : NestedScrollConnection {
+
+ /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
+ private var isPriorityMode = false
+
+ private var offsetScrolledBeforePriorityMode = Offset.Zero
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource,
+ ): Offset {
+ // The offset before the start takes into account the up and down movements, starting from
+ // the beginning or from the last fling gesture.
+ val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+
+ if (
+ isPriorityMode ||
+ source == NestedScrollSource.Fling ||
+ !canStart(available, offsetBeforeStart)
+ ) {
+ // The priority mode cannot start so we won't consume the available offset.
+ return Offset.Zero
+ }
+
+ // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+ // available offset following a scroll event.
+ isPriorityMode = true
+
+ // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+ // lifted (step 3b), or this object has been destroyed (step 3c).
+ onStart()
+
+ return onScroll(available)
+ }
+
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ if (!isPriorityMode) {
+ if (source != NestedScrollSource.Fling) {
+ // We want to track the amount of offset consumed before entering priority mode
+ offsetScrolledBeforePriorityMode += available
+ }
+
+ return Offset.Zero
+ }
+
+ if (!canContinueScroll()) {
+ // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+ onPriorityStop(velocity = Velocity.Zero)
+ return Offset.Zero
+ }
+
+ // Step 2: We have the priority and can consume the scroll events.
+ return onScroll(available)
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
+ // of the fling gesture.
+ return onPriorityStop(velocity = available)
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ return onPostFling(available)
+ }
+
+ /** Method to call before destroying the object or to reset the initial state. */
+ fun reset() {
+ // Step 3c: To ensure that an onStop is always called for every onStart.
+ onPriorityStop(velocity = Velocity.Zero)
+ }
+
+ private fun onPriorityStop(velocity: Velocity): Velocity {
+
+ // We can restart tracking the consumed offsets from scratch.
+ offsetScrolledBeforePriorityMode = Offset.Zero
+
+ if (!isPriorityMode) {
+ return Velocity.Zero
+ }
+
+ isPriorityMode = false
+
+ return onStop(velocity)
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
new file mode 100644
index 000000000000..03d231a7fcc6
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
+ val scrollSource = testCase.scrollSource
+
+ private var height = 0f
+
+ private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
+ LargeTopAppBarNestedScrollConnection(
+ height = { height },
+ onHeightChanged = { height = it },
+ heightRange = heightRange,
+ )
+
+ @Test
+ fun onScrollUp_consumeHeightFirst() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+ // It can decrease by 1 the height
+ assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f))
+ assertThat(height).isEqualTo(0f)
+ }
+
+ @Test
+ fun onScrollUp_consumeDownToMin() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 0f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+ // It should not change the height (already at min)
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(0f)
+ }
+
+ @Test
+ fun onScrollUp_ignorePostScroll() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = -1f),
+ source = scrollSource
+ )
+
+ // It should ignore all onPostScroll events
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(1f)
+ }
+
+ @Test
+ fun onScrollDown_allowConsumeContentFirst() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = scrollSource)
+
+ // It should ignore all onPreScroll events
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(1f)
+ }
+
+ @Test
+ fun onScrollDown_consumeHeightPostScroll() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = 1f),
+ source = scrollSource
+ )
+
+ // It can increase by 1 the height
+ assertThat(offsetConsumed).isEqualTo(Offset(0f, 1f))
+ assertThat(height).isEqualTo(2f)
+ }
+
+ @Test
+ fun onScrollDown_consumeUpToMax() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 2f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = 1f),
+ source = scrollSource
+ )
+
+ // It should not change the height (already at max)
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(2f)
+ }
+
+ // NestedScroll Source is a value/inline class and must be wrapped in a parameterized test
+ // https://youtrack.jetbrains.com/issue/KT-35523/Parameterized-JUnit-tests-with-inline-classes-throw-IllegalArgumentException
+ data class TestCase(val scrollSource: NestedScrollSource) {
+ override fun toString() = scrollSource.toString()
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ TestCase(NestedScrollSource.Drag),
+ TestCase(NestedScrollSource.Fling),
+ TestCase(NestedScrollSource.Wheel),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
new file mode 100644
index 000000000000..8e2b77a2f2a0
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PriorityPostNestedScrollConnectionTest {
+ private var canStart = false
+ private var canContinueScroll = false
+ private var isStarted = false
+ private var lastScroll: Offset? = null
+ private var returnOnScroll = Offset.Zero
+ private var lastStop: Velocity? = null
+ private var returnOnStop = Velocity.Zero
+ private var lastOnPostFling: Velocity? = null
+ private var returnOnPostFling = Velocity.Zero
+
+ private val scrollConnection =
+ PriorityPostNestedScrollConnection(
+ canStart = { _, _ -> canStart },
+ canContinueScroll = { canContinueScroll },
+ onStart = { isStarted = true },
+ onScroll = {
+ lastScroll = it
+ returnOnScroll
+ },
+ onStop = {
+ lastStop = it
+ returnOnStop
+ },
+ onPostFling = {
+ lastOnPostFling = it
+ returnOnPostFling
+ },
+ )
+
+ private val offset1 = Offset(1f, 1f)
+ private val offset2 = Offset(2f, 2f)
+ private val velocity1 = Velocity(1f, 1f)
+ private val velocity2 = Velocity(2f, 2f)
+
+ private fun startPriorityMode() {
+ canStart = true
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
+ canStart = true
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ startPriorityMode()
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyIfAllowed() {
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ startPriorityMode()
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ @Test
+ fun step1_onPriorityModeStarted_receiveAvailableOffset() {
+ canStart = true
+
+ scrollConnection.onPostScroll(
+ consumed = offset1,
+ available = offset2,
+ source = NestedScrollSource.Drag
+ )
+
+ assertThat(lastScroll).isEqualTo(offset2)
+ }
+
+ @Test
+ fun step2_onPriorityMode_shouldContinueIfAllowed() {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
+ assertThat(lastScroll).isEqualTo(offset1)
+
+ canContinueScroll = false
+ scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
+ assertThat(lastScroll).isNotEqualTo(offset2)
+ assertThat(lastScroll).isEqualTo(offset1)
+ }
+
+ @Test
+ fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
+ startPriorityMode()
+ canContinueScroll = false
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun step3c_onPriorityMode_shouldStopOnReset() {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.reset()
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun receive_onPostFling() = runTest {
+ scrollConnection.onPostFling(
+ consumed = velocity1,
+ available = velocity2,
+ )
+
+ assertThat(lastOnPostFling).isEqualTo(velocity2)
+ }
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ca30e159a0ff..a6517c1dde1b 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@ android_library {
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
+ "SystemUISharedLib-Keyguard",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/shared/keyguard/Android.bp b/packages/SystemUI/shared/keyguard/Android.bp
new file mode 100644
index 000000000000..2181439ae1fc
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/Android.bp
@@ -0,0 +1,16 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUISharedLib-Keyguard",
+ srcs: [
+ "src/**/*.java",
+ ],
+ min_sdk_version: "current",
+}
diff --git a/packages/SystemUI/shared/keyguard/AndroidManifest.xml b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
new file mode 100644
index 000000000000..49bee08dff02
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.shared.keyguard">
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
new file mode 100644
index 000000000000..fe12134054c4
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
@@ -0,0 +1,239 @@
+/*
+ * 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.keyguard;
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+/**
+ * A View similar to a textView which contains password text and can animate when the text is
+ * changed
+ */
+public abstract class BasePasswordTextView extends FrameLayout {
+ private String mText = "";
+ private UserActivityListener mUserActivityListener;
+ protected boolean mIsPinHinting;
+ protected PinShapeInput mPinShapeInput;
+ protected boolean mShowPassword = true;
+ protected boolean mUsePinShapes = false;
+ protected static final char DOT = '\u2022';
+
+ /** Listens to user activities like appending, deleting and resetting PIN text */
+ public interface UserActivityListener {
+
+ /** Listens to user activities. */
+ void onUserActivity();
+ }
+
+ public BasePasswordTextView(Context context) {
+ this(context, null);
+ }
+
+ public BasePasswordTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BasePasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BasePasswordTextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ protected abstract PinShapeInput inflatePinShapeInput(boolean isPinHinting);
+
+ protected abstract boolean shouldSendAccessibilityEvent();
+
+ protected void onAppend(char c, int newLength) {}
+
+ protected void onDelete(int index) {}
+
+ protected void onReset(boolean animated) {}
+
+ @CallSuper
+ protected void onUserActivity() {
+ if (mUserActivityListener != null) {
+ mUserActivityListener.onUserActivity();
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /** Appends a PIN text */
+ public void append(char c) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = mText + c;
+ int newLength = mText.length();
+ onAppend(c, newLength);
+
+ if (mPinShapeInput != null) {
+ mPinShapeInput.append();
+ }
+
+ onUserActivity();
+
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+ }
+
+ /** Sets a listener who is notified on user activity */
+ public void setUserActivityListener(UserActivityListener userActivityListener) {
+ mUserActivityListener = userActivityListener;
+ }
+
+ /** Deletes the last PIN text */
+ public void deleteLastChar() {
+ int length = mText.length();
+ if (length > 0) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = mText.substring(0, length - 1);
+ onDelete(length - 1);
+
+ if (mPinShapeInput != null) {
+ mPinShapeInput.delete();
+ }
+
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
+ }
+ onUserActivity();
+ }
+
+ /** Gets entered PIN text */
+ public String getText() {
+ return mText;
+ }
+
+ /** Gets a transformed text for accessibility event. Called before text changed. */
+ protected CharSequence getTransformedText() {
+ return String.valueOf(DOT).repeat(mText.length());
+ }
+
+ /** Gets a transformed text for accessibility event. Called after text changed. */
+ protected CharSequence getTransformedText(int fromIndex, int removedCount, int addedCount) {
+ return getTransformedText();
+ }
+
+ /** Reset PIN text without error */
+ public void reset(boolean animated, boolean announce) {
+ reset(false /* error */, animated, announce);
+ }
+
+ /** Reset PIN text */
+ public void reset(boolean error, boolean animated, boolean announce) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = "";
+
+ onReset(animated);
+ if (animated) {
+ onUserActivity();
+ }
+
+ if (mPinShapeInput != null) {
+ if (error) {
+ mPinShapeInput.resetWithError();
+ } else {
+ mPinShapeInput.reset();
+ }
+ }
+
+ if (announce) {
+ sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
+ }
+ }
+
+ void sendAccessibilityEventTypeViewTextChanged(
+ CharSequence beforeText, int fromIndex, int removedCount, int addedCount) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && shouldSendAccessibilityEvent()) {
+ AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setFromIndex(fromIndex);
+ event.setRemovedCount(removedCount);
+ event.setAddedCount(addedCount);
+ event.setBeforeText(beforeText);
+ CharSequence transformedText = getTransformedText(fromIndex, removedCount, addedCount);
+ if (!TextUtils.isEmpty(transformedText)) {
+ event.getText().add(transformedText);
+ }
+ event.setPassword(true);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
+ /** Sets whether to use pin shapes. */
+ public void setUsePinShapes(boolean usePinShapes) {
+ mUsePinShapes = usePinShapes;
+ }
+
+ /** Determines whether AutoConfirmation feature is on. */
+ public void setIsPinHinting(boolean isPinHinting) {
+ // Do not reinflate the view if we are using the same one.
+ if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
+ return;
+ }
+ mIsPinHinting = isPinHinting;
+
+ if (mPinShapeInput != null) {
+ removeView(mPinShapeInput.getView());
+ mPinShapeInput = null;
+ }
+
+ mPinShapeInput = inflatePinShapeInput(isPinHinting);
+ addView(mPinShapeInput.getView());
+ }
+
+ /** Controls whether the last entered digit is briefly shown after being entered */
+ public void setShowPassword(boolean enabled) {
+ mShowPassword = enabled;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ event.setClassName(EditText.class.getName());
+ event.setPassword(true);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ info.setClassName(EditText.class.getName());
+ info.setPassword(true);
+ info.setText(getTransformedText());
+
+ info.setEditable(true);
+
+ info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
index 52ae6bac6d74..d2b4066f8b60 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
@@ -44,6 +44,14 @@ public interface PinShapeInput {
void reset();
/**
+ * This is the method that is triggered for resetting the view with error If it doesn't have to
+ * show something regarding error, just reset
+ */
+ default void resetWithError() {
+ reset();
+ }
+
+ /**
* This is the method that is triggered for getting the view
*/
View getView();
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 8e8ee48aba83..9a2ffe0ec68c 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.keyguard;
@@ -30,18 +30,11 @@ import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.text.InputType;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.widget.EditText;
-import android.widget.FrameLayout;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -52,12 +45,11 @@ import java.util.ArrayList;
* A View similar to a textView which contains password text and can animate when the text is
* changed
*/
-public class PasswordTextView extends FrameLayout {
-
- private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
- private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
+public class PasswordTextView extends BasePasswordTextView {
public static final long APPEAR_DURATION = 160;
public static final long DISAPPEAR_DURATION = 160;
+ private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
+ private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
private static final long RESET_DELAY_PER_ELEMENT = 40;
private static final long RESET_MAX_DELAY = 200;
@@ -82,15 +74,12 @@ public class PasswordTextView extends FrameLayout {
*/
private static final float OVERSHOOT_TIME_POSITION = 0.5f;
- private static char DOT = '\u2022';
-
/**
* The raw text size, will be multiplied by the scaled density when drawn
*/
private int mTextHeightRaw;
private final int mGravity;
private ArrayList<CharState> mTextChars = new ArrayList<>();
- private String mText = "";
private int mDotSize;
private PowerManager mPM;
private int mCharPadding;
@@ -99,15 +88,6 @@ public class PasswordTextView extends FrameLayout {
private Interpolator mAppearInterpolator;
private Interpolator mDisappearInterpolator;
private Interpolator mFastOutSlowInInterpolator;
- private boolean mShowPassword = true;
- private UserActivityListener mUserActivityListener;
- private boolean mIsPinHinting;
- private PinShapeInput mPinShapeInput;
- private boolean mUsePinShapes = false;
-
- public interface UserActivityListener {
- void onUserActivity();
- }
public PasswordTextView(Context context) {
this(context, null);
@@ -145,8 +125,7 @@ public class PasswordTextView extends FrameLayout {
mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
getContext().getResources().getDimensionPixelSize(
R.dimen.password_char_padding));
- mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor,
- Color.WHITE);
+ mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor, Color.WHITE);
mDrawPaint.setColor(mDrawColor);
} finally {
@@ -156,8 +135,7 @@ public class PasswordTextView extends FrameLayout {
mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mDrawPaint.setTextAlign(Paint.Align.CENTER);
mDrawPaint.setTypeface(Typeface.create(
- context.getString(com.android.internal.R.string.config_headlineFontFamily),
- 0));
+ context.getString(com.android.internal.R.string.config_headlineFontFamily), 0));
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
@@ -169,9 +147,19 @@ public class PasswordTextView extends FrameLayout {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- mTextHeightRaw = getContext().getResources().getInteger(
- R.integer.scaled_password_text_size);
+ protected PinShapeInput inflatePinShapeInput(boolean isPinHinting) {
+ if (isPinHinting) {
+ return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_hinting_view, null);
+ } else {
+ return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_non_hinting_view, null);
+ }
+ }
+
+ @Override
+ protected boolean shouldSendAccessibilityEvent() {
+ return isFocused() || isSelected() && isShown();
}
@Override
@@ -201,8 +189,8 @@ public class PasswordTextView extends FrameLayout {
int charHeight = (bounds.bottom - bounds.top);
float yPosition =
(getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
- canvas.clipRect(getPaddingLeft(), getPaddingTop(),
- getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+ canvas.clipRect(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+ getHeight() - getPaddingBottom());
float charLength = bounds.right - bounds.left;
for (int i = 0; i < length; i++) {
CharState charState = mTextChars.get(i);
@@ -212,52 +200,9 @@ public class PasswordTextView extends FrameLayout {
}
}
- /**
- * Reload colors from resources.
- **/
- public void reloadColors() {
- mDrawColor = Utils.getColorAttr(getContext(),
- android.R.attr.textColorPrimary).getDefaultColor();
- mDrawPaint.setColor(mDrawColor);
- if (mPinShapeInput != null) {
- mPinShapeInput.setDrawColor(mDrawColor);
- }
- }
-
@Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- private Rect getCharBounds() {
- float textHeight = mTextHeightRaw * getResources().getDisplayMetrics().scaledDensity;
- mDrawPaint.setTextSize(textHeight);
- Rect bounds = new Rect();
- mDrawPaint.getTextBounds("0", 0, 1, bounds);
- return bounds;
- }
-
- private float getDrawingWidth() {
- int width = 0;
- int length = mTextChars.size();
- Rect bounds = getCharBounds();
- int charLength = bounds.right - bounds.left;
- for (int i = 0; i < length; i++) {
- CharState charState = mTextChars.get(i);
- if (i != 0) {
- width += mCharPadding * charState.currentWidthFactor;
- }
- width += charLength * charState.currentWidthFactor;
- }
- return width;
- }
-
-
- public void append(char c) {
+ protected void onAppend(char c, int newLength) {
int visibleChars = mTextChars.size();
- CharSequence textbefore = getTransformedText();
- mText = mText + c;
- int newLength = mText.length();
CharState charState;
if (newLength > visibleChars) {
charState = obtainCharState(c);
@@ -266,9 +211,6 @@ public class PasswordTextView extends FrameLayout {
charState = mTextChars.get(newLength - 1);
charState.whichChar = c;
}
- if (mPinShapeInput != null) {
- mPinShapeInput.append();
- }
charState.startAppearAnimation();
// ensure that the previous element is being swapped
@@ -278,70 +220,22 @@ public class PasswordTextView extends FrameLayout {
previousState.swapToDotWhenAppearFinished();
}
}
- userActivity();
- sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
- }
-
- public void setUserActivityListener(UserActivityListener userActivityListener) {
- mUserActivityListener = userActivityListener;
- }
-
- private void userActivity() {
- mPM.userActivity(SystemClock.uptimeMillis(), false);
- if (mUserActivityListener != null) {
- mUserActivityListener.onUserActivity();
- }
- }
-
- public void deleteLastChar() {
- int length = mText.length();
- CharSequence textbefore = getTransformedText();
- if (length > 0) {
- mText = mText.substring(0, length - 1);
- CharState charState = mTextChars.get(length - 1);
- charState.startRemoveAnimation(0, 0);
- sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
- if (mPinShapeInput != null) {
- mPinShapeInput.delete();
- }
- }
- userActivity();
- }
-
- public String getText() {
- return mText;
- }
-
- private CharSequence getTransformedText() {
- int textLength = mTextChars.size();
- StringBuilder stringBuilder = new StringBuilder(textLength);
- for (int i = 0; i < textLength; i++) {
- CharState charState = mTextChars.get(i);
- // If the dot is disappearing, the character is disappearing entirely. Consider
- // it gone.
- if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) {
- continue;
- }
- stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT);
- }
- return stringBuilder;
}
- private CharState obtainCharState(char c) {
- CharState charState = new CharState();
- charState.whichChar = c;
- return charState;
+ @Override
+ protected void onDelete(int index) {
+ CharState charState = mTextChars.get(index);
+ charState.startRemoveAnimation(0, 0);
}
- public void reset(boolean animated, boolean announce) {
- CharSequence textbefore = getTransformedText();
- mText = "";
- int length = mTextChars.size();
- int middleIndex = (length - 1) / 2;
- long delayPerElement = RESET_DELAY_PER_ELEMENT;
- for (int i = 0; i < length; i++) {
- CharState charState = mTextChars.get(i);
- if (animated) {
+ @Override
+ protected void onReset(boolean animated) {
+ if (animated) {
+ int length = mTextChars.size();
+ int middleIndex = (length - 1) / 2;
+ long delayPerElement = RESET_DELAY_PER_ELEMENT;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
int delayIndex;
if (i <= middleIndex) {
delayIndex = i * 2;
@@ -356,99 +250,78 @@ public class PasswordTextView extends FrameLayout {
charState.startRemoveAnimation(startDelay, maxDelay);
charState.removeDotSwapCallbacks();
}
- }
- if (!animated) {
- mTextChars.clear();
} else {
- userActivity();
- }
- if (mPinShapeInput != null) {
- mPinShapeInput.reset();
- }
- if (announce) {
- sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
- }
- }
-
- void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
- int removedCount, int addedCount) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
- AccessibilityEvent event =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
- event.setFromIndex(fromIndex);
- event.setRemovedCount(removedCount);
- event.setAddedCount(addedCount);
- event.setBeforeText(beforeText);
- CharSequence transformedText = getTransformedText();
- if (!TextUtils.isEmpty(transformedText)) {
- event.getText().add(transformedText);
- }
- event.setPassword(true);
- sendAccessibilityEventUnchecked(event);
+ mTextChars.clear();
}
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ protected void onUserActivity() {
+ mPM.userActivity(SystemClock.uptimeMillis(), false);
+ super.onUserActivity();
+ }
- event.setClassName(EditText.class.getName());
- event.setPassword(true);
+ /**
+ * Reload colors from resources.
+ **/
+ public void reloadColors() {
+ mDrawColor = Utils.getColorAttr(getContext(),
+ android.R.attr.textColorPrimary).getDefaultColor();
+ mDrawPaint.setColor(mDrawColor);
+ if (mPinShapeInput != null) {
+ mPinShapeInput.setDrawColor(mDrawColor);
+ }
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.setClassName(EditText.class.getName());
- info.setPassword(true);
- info.setText(getTransformedText());
-
- info.setEditable(true);
-
- info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mTextHeightRaw = getContext().getResources().getInteger(
+ R.integer.scaled_password_text_size);
}
- /**
- * Sets whether to use pin shapes.
- */
- public void setUsePinShapes(boolean usePinShapes) {
- mUsePinShapes = usePinShapes;
+ private Rect getCharBounds() {
+ float textHeight = mTextHeightRaw * getResources().getDisplayMetrics().scaledDensity;
+ mDrawPaint.setTextSize(textHeight);
+ Rect bounds = new Rect();
+ mDrawPaint.getTextBounds("0", 0, 1, bounds);
+ return bounds;
}
- /**
- * Determines whether AutoConfirmation feature is on.
- *
- * @param isPinHinting
- */
- public void setIsPinHinting(boolean isPinHinting) {
- // Do not reinflate the view if we are using the same one.
- if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
- return;
- }
- mIsPinHinting = isPinHinting;
-
- if (mPinShapeInput != null) {
- removeView(mPinShapeInput.getView());
- mPinShapeInput = null;
+ private float getDrawingWidth() {
+ int width = 0;
+ int length = mTextChars.size();
+ Rect bounds = getCharBounds();
+ int charLength = bounds.right - bounds.left;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
+ if (i != 0) {
+ width += mCharPadding * charState.currentWidthFactor;
+ }
+ width += charLength * charState.currentWidthFactor;
}
+ return width;
+ }
- if (isPinHinting) {
- mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_pin_shape_hinting_view, null);
- } else {
- mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_pin_shape_non_hinting_view, null);
- }
- addView(mPinShapeInput.getView());
+ private CharState obtainCharState(char c) {
+ CharState charState = new CharState();
+ charState.whichChar = c;
+ return charState;
}
- /**
- * Controls whether the last entered digit is briefly shown after being entered
- */
- public void setShowPassword(boolean enabled) {
- mShowPassword = enabled;
+ @Override
+ protected CharSequence getTransformedText() {
+ int textLength = mTextChars.size();
+ StringBuilder stringBuilder = new StringBuilder(textLength);
+ for (int i = 0; i < textLength; i++) {
+ CharState charState = mTextChars.get(i);
+ // If the dot is disappearing, the character is disappearing entirely. Consider
+ // it gone.
+ if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) {
+ continue;
+ }
+ stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT);
+ }
+ return stringBuilder;
}
private class CharState {
@@ -468,6 +341,7 @@ public class PasswordTextView extends FrameLayout {
Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
@@ -516,53 +390,53 @@ public class PasswordTextView extends FrameLayout {
}
};
- private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentDotSizeFactor = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
-
- private ValueAnimator.AnimatorUpdateListener textSizeUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- boolean textVisibleBefore = isCharVisibleForA11y();
- float beforeTextSizeFactor = currentTextSizeFactor;
- currentTextSizeFactor = (float) animation.getAnimatedValue();
- if (textVisibleBefore != isCharVisibleForA11y()) {
- currentTextSizeFactor = beforeTextSizeFactor;
- CharSequence beforeText = getTransformedText();
- currentTextSizeFactor = (float) animation.getAnimatedValue();
- int indexOfThisChar = mTextChars.indexOf(CharState.this);
- if (indexOfThisChar >= 0) {
- sendAccessibilityEventTypeViewTextChanged(
- beforeText, indexOfThisChar, 1, 1);
+ private ValueAnimator.AnimatorUpdateListener mDotSizeUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentDotSizeFactor = (float) animation.getAnimatedValue();
+ invalidate();
}
- }
- invalidate();
- }
- };
-
- private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentTextTranslationY = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
-
- private ValueAnimator.AnimatorUpdateListener widthUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentWidthFactor = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
+ };
+
+ private ValueAnimator.AnimatorUpdateListener mTextSizeUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ boolean textVisibleBefore = isCharVisibleForA11y();
+ float beforeTextSizeFactor = currentTextSizeFactor;
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ if (textVisibleBefore != isCharVisibleForA11y()) {
+ currentTextSizeFactor = beforeTextSizeFactor;
+ CharSequence beforeText = getTransformedText();
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ int indexOfThisChar = mTextChars.indexOf(CharState.this);
+ if (indexOfThisChar >= 0) {
+ sendAccessibilityEventTypeViewTextChanged(beforeText,
+ indexOfThisChar, 1, 1);
+ }
+ }
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener mTextTranslationUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentTextTranslationY = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener mWidthUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentWidthFactor = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
private Runnable dotSwapperRunnable = new Runnable() {
@Override
@@ -573,12 +447,15 @@ public class PasswordTextView extends FrameLayout {
};
void startRemoveAnimation(long startDelay, long widthDelay) {
- boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
- || (dotAnimator != null && dotAnimationIsGrowing);
- boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
- || (textAnimator != null && textAnimationIsGrowing);
- boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
- || (widthAnimator != null && widthAnimationIsGrowing);
+ boolean dotNeedsAnimation =
+ (currentDotSizeFactor > 0.0f && dotAnimator == null) || (dotAnimator != null
+ && dotAnimationIsGrowing);
+ boolean textNeedsAnimation =
+ (currentTextSizeFactor > 0.0f && textAnimator == null) || (textAnimator != null
+ && textAnimationIsGrowing);
+ boolean widthNeedsAnimation =
+ (currentWidthFactor > 0.0f && widthAnimator == null) || (widthAnimator != null
+ && widthAnimationIsGrowing);
if (dotNeedsAnimation) {
startDotDisappearAnimation(startDelay);
}
@@ -591,10 +468,10 @@ public class PasswordTextView extends FrameLayout {
}
void startAppearAnimation() {
- boolean dotNeedsAnimation = !mShowPassword
- && (dotAnimator == null || !dotAnimationIsGrowing);
- boolean textNeedsAnimation = mShowPassword
- && (textAnimator == null || !textAnimationIsGrowing);
+ boolean dotNeedsAnimation =
+ !mShowPassword && (dotAnimator == null || !dotAnimationIsGrowing);
+ boolean textNeedsAnimation =
+ mShowPassword && (textAnimator == null || !textAnimationIsGrowing);
boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
if (dotNeedsAnimation) {
startDotAppearAnimation(0);
@@ -628,8 +505,8 @@ public class PasswordTextView extends FrameLayout {
void swapToDotWhenAppearFinished() {
removeDotSwapCallbacks();
if (textAnimator != null) {
- long remainingDuration = textAnimator.getDuration()
- - textAnimator.getCurrentPlayTime();
+ long remainingDuration =
+ textAnimator.getDuration() - textAnimator.getCurrentPlayTime();
postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
} else {
performSwap();
@@ -638,14 +515,14 @@ public class PasswordTextView extends FrameLayout {
private void performSwap() {
startTextDisappearAnimation(0);
- startDotAppearAnimation(DISAPPEAR_DURATION
- - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
+ startDotAppearAnimation(
+ DISAPPEAR_DURATION - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
}
private void startWidthDisappearAnimation(long widthDelay) {
cancelAnimator(widthAnimator);
widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
- widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addUpdateListener(mWidthUpdater);
widthAnimator.addListener(widthFinishListener);
widthAnimator.addListener(removeEndListener);
widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
@@ -657,7 +534,7 @@ public class PasswordTextView extends FrameLayout {
private void startTextDisappearAnimation(long startDelay) {
cancelAnimator(textAnimator);
textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
- textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addUpdateListener(mTextSizeUpdater);
textAnimator.addListener(textFinishListener);
textAnimator.setInterpolator(mDisappearInterpolator);
textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
@@ -669,7 +546,7 @@ public class PasswordTextView extends FrameLayout {
private void startDotDisappearAnimation(long startDelay) {
cancelAnimator(dotAnimator);
ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
- animator.addUpdateListener(dotSizeUpdater);
+ animator.addUpdateListener(mDotSizeUpdater);
animator.addListener(dotFinishListener);
animator.setInterpolator(mDisappearInterpolator);
long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
@@ -683,7 +560,7 @@ public class PasswordTextView extends FrameLayout {
private void startWidthAppearAnimation() {
cancelAnimator(widthAnimator);
widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
- widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addUpdateListener(mWidthUpdater);
widthAnimator.addListener(widthFinishListener);
widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
widthAnimator.start();
@@ -693,7 +570,7 @@ public class PasswordTextView extends FrameLayout {
private void startTextAppearAnimation() {
cancelAnimator(textAnimator);
textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
- textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addUpdateListener(mTextSizeUpdater);
textAnimator.addListener(textFinishListener);
textAnimator.setInterpolator(mAppearInterpolator);
textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
@@ -703,7 +580,7 @@ public class PasswordTextView extends FrameLayout {
// handle translation
if (textTranslateAnimator == null) {
textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
- textTranslateAnimator.addUpdateListener(textTranslationUpdater);
+ textTranslateAnimator.addUpdateListener(mTextTranslationUpdater);
textTranslateAnimator.addListener(textTranslateFinishListener);
textTranslateAnimator.setInterpolator(mAppearInterpolator);
textTranslateAnimator.setDuration(APPEAR_DURATION);
@@ -717,14 +594,14 @@ public class PasswordTextView extends FrameLayout {
// We perform an overshoot animation
ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
DOT_OVERSHOOT_FACTOR);
- overShootAnimator.addUpdateListener(dotSizeUpdater);
+ overShootAnimator.addUpdateListener(mDotSizeUpdater);
overShootAnimator.setInterpolator(mAppearInterpolator);
- long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
- * OVERSHOOT_TIME_POSITION);
+ long overShootDuration =
+ (long) (DOT_APPEAR_DURATION_OVERSHOOT * OVERSHOOT_TIME_POSITION);
overShootAnimator.setDuration(overShootDuration);
ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
1.0f);
- settleBackAnimator.addUpdateListener(dotSizeUpdater);
+ settleBackAnimator.addUpdateListener(mDotSizeUpdater);
settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
settleBackAnimator.addListener(dotFinishListener);
AnimatorSet animatorSet = new AnimatorSet();
@@ -734,7 +611,7 @@ public class PasswordTextView extends FrameLayout {
dotAnimator = animatorSet;
} else {
ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
- growAnimator.addUpdateListener(dotSizeUpdater);
+ growAnimator.addUpdateListener(mDotSizeUpdater);
growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
growAnimator.addListener(dotFinishListener);
growAnimator.setStartDelay(delay);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index eec16e6dc301..c9801d77fa43 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -52,6 +52,7 @@ import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
@@ -184,6 +185,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
protected void setListening(boolean listening) {
mListening = listening;
if (listening) {
+ // System UI could be restarted while ops are active, so fetch the currently active ops
+ // once System UI starts listening again.
+ fetchCurrentActiveOps();
+
mAppOps.startWatchingActive(OPS, this);
mAppOps.startWatchingNoted(OPS, this);
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
@@ -216,6 +221,29 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
}
}
+ private void fetchCurrentActiveOps() {
+ List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
+ for (AppOpsManager.PackageOps op : packageOps) {
+ for (AppOpsManager.OpEntry entry : op.getOps()) {
+ for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
+ entry.getAttributedOpEntries().entrySet()) {
+ if (attributedOpEntry.getValue().isRunning()) {
+ onOpActiveChanged(
+ entry.getOpStr(),
+ op.getUid(),
+ op.getPackageName(),
+ /* attributionTag= */ attributedOpEntry.getKey(),
+ /* active= */ true,
+ // AppOpsManager doesn't have a way to fetch attribution flags or
+ // chain ID given an op entry, so default them to none.
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ }
+ }
+ }
+ }
+ }
+
/**
* Adds a callback that will get notifified when an AppOp of the type the controller tracks
* changes
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 94e563319524..88b9612e8efd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.dagger
-import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.controller.ControlsController
@@ -44,10 +43,9 @@ class ControlsComponent
@Inject
constructor(
@ControlsFeatureEnabled private val featureEnabled: Boolean,
- private val context: Context,
- private val lazyControlsController: Lazy<ControlsController>,
- private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>,
+ lazyControlsController: Lazy<ControlsController>,
+ lazyControlsUiController: Lazy<ControlsUiController>,
+ lazyControlsListingController: Lazy<ControlsListingController>,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -55,27 +53,25 @@ constructor(
optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
) {
+ private val controlsController: Optional<ControlsController> =
+ optionalIf(isEnabled(), lazyControlsController)
+ private val controlsUiController: Optional<ControlsUiController> =
+ optionalIf(isEnabled(), lazyControlsUiController)
+ private val controlsListingController: Optional<ControlsListingController> =
+ optionalIf(isEnabled(), lazyControlsListingController)
+
val canShowWhileLockedSetting: StateFlow<Boolean> =
controlsSettingsRepository.canShowControlsInLockscreen
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl())
- fun getControlsController(): Optional<ControlsController> {
- return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
- }
+ fun getControlsController(): Optional<ControlsController> = controlsController
- fun getControlsUiController(): Optional<ControlsUiController> {
- return if (featureEnabled) Optional.of(lazyControlsUiController.get()) else Optional.empty()
- }
+ fun getControlsUiController(): Optional<ControlsUiController> = controlsUiController
- fun getControlsListingController(): Optional<ControlsListingController> {
- return if (featureEnabled) {
- Optional.of(lazyControlsListingController.get())
- } else {
- Optional.empty()
- }
- }
+ fun getControlsListingController(): Optional<ControlsListingController> =
+ controlsListingController
/** @return true if controls are feature-enabled and the user has the setting enabled */
fun isEnabled() = featureEnabled
@@ -118,4 +114,11 @@ constructor(
fun getTileImageId(): Int {
return controlsTileResourceConfiguration.getTileImageId()
}
+
+ private fun <T : Any> optionalIf(condition: Boolean, provider: Lazy<T>): Optional<T> =
+ if (condition) {
+ Optional.of(provider.get())
+ } else {
+ Optional.empty()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 5a1ad96da7a9..e1e1aaef6f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -35,11 +35,14 @@ import android.os.SystemClock;
import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Slog;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
@@ -51,17 +54,13 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Optional;
import java.util.concurrent.Future;
import javax.inject.Inject;
-import dagger.Lazy;
-
@SysUISingleton
public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
@@ -107,12 +106,15 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
@VisibleForTesting int mBatteryLevel = 100;
@VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ private boolean mInVrMode;
+
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
- private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+ @Nullable
+ private final IVrManager mVrManager;
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
new WakefulnessLifecycle.Observer() {
@Override
@@ -134,17 +136,28 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
}
};
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ mInVrMode = enabled;
+ }
+ };
+
@Inject
- public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
- CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+ public PowerUI(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ CommandQueue commandQueue,
+ @Nullable IVrManager vrManager,
+ WarningsUI warningsUI,
+ EnhancedEstimates enhancedEstimates,
WakefulnessLifecycle wakefulnessLifecycle,
PowerManager powerManager,
UserTracker userTracker) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mVrManager = vrManager;
mWarnings = warningsUI;
mEnhancedEstimates = enhancedEstimates;
mPowerManager = powerManager;
@@ -164,7 +177,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
};
final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
@@ -199,6 +212,14 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
});
initThermalEventListeners();
mCommandQueue.addCallback(this);
+
+ if (mVrManager != null) {
+ try {
+ mVrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
+ }
}
@Override
@@ -718,10 +739,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
int status = temp.getStatus();
if (status >= Temperature.THROTTLING_EMERGENCY) {
- final Optional<CentralSurfaces> centralSurfacesOptional =
- mCentralSurfacesOptionalLazy.get();
- if (!centralSurfacesOptional.map(CentralSurfaces::isDeviceInVrMode)
- .orElse(false)) {
+ if (!mInVrMode) {
mWarnings.showHighTemperatureWarning();
Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
+ ", current skin status = " + status
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index f40f57099110..a3bc00248362 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -132,8 +132,9 @@ constructor(
override fun onStatusEvent(event: StatusEvent) {
Assert.isMainThread()
- // Ignore any updates until the system is up and running
- if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ // Ignore any updates until the system is up and running. However, for important events that
+ // request to be force visible (like privacy), ignore whether it's too early.
+ if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
index 5fa83ef5d454..6b5a548b0afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -93,8 +93,9 @@ constructor(
@SystemAnimationState override fun getAnimationState() = animationState
override fun onStatusEvent(event: StatusEvent) {
- // Ignore any updates until the system is up and running
- if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ // Ignore any updates until the system is up and running. However, for important events that
+ // request to be force visible (like privacy), ignore whether it's too early.
+ if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
return
}
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 cbb39150392c..0ff1a9561748 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -200,8 +200,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
void onKeyguardViewManagerStatesUpdated();
- boolean isDeviceInVrMode();
-
NotificationPresenter getPresenter();
/**
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 10422e31dfaa..37038a357479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -41,7 +41,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
override fun isLaunchingActivityOverLockscreen() = false
override fun onKeyguardViewManagerStatesUpdated() {}
- override fun isDeviceInVrMode() = false
override fun getPresenter(): NotificationPresenter? = null
override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {}
override fun getCommandQueuePanelsEnabled() = false
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 d91f37504c2e..8cdf7d8a100d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1667,11 +1667,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
@Override
- public boolean isDeviceInVrMode() {
- return mPresenter.isDeviceInVrMode();
- }
-
- @Override
public NotificationPresenter getPresenter() {
return mPresenter;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 4b2fb4373957..249ca35b610a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -26,6 +26,7 @@ import android.widget.ImageView
import android.widget.Space
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
@@ -64,7 +65,7 @@ object MobileIconBinder {
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
- view.isVisible = true
+ view.isVisible = viewModel.isVisible.value
iconView.isVisible = true
// TODO(b/238425913): We should log this visibility state.
@@ -77,108 +78,122 @@ object MobileIconBinder {
var isCollecting = false
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- logger.logCollectionStarted(view, viewModel)
- isCollecting = true
-
- launch {
- visibilityState.collect { state ->
- when (state) {
- STATE_ICON -> {
- mobileGroupView.visibility = VISIBLE
- dotView.visibility = GONE
- }
- STATE_DOT -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = VISIBLE
- }
- STATE_HIDDEN -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = INVISIBLE
- }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ // isVisible controls the visibility state of the outer group, and thus it needs
+ // to run in the CREATED lifecycle so it can continue to watch while invisible
+ // See (b/291031862) for details
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ viewModel.verboseLogger?.logBinderReceivedVisibility(
+ view,
+ viewModel.subscriptionId,
+ isVisible
+ )
+ view.isVisible = isVisible
+ // [StatusIconContainer] can get out of sync sometimes. Make sure to
+ // request another layout when this changes.
+ view.requestLayout()
}
}
}
+ }
- launch {
- viewModel.isVisible.collect { isVisible ->
- viewModel.verboseLogger?.logBinderReceivedVisibility(
- view,
- viewModel.subscriptionId,
- isVisible
- )
- view.isVisible = isVisible
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ logger.logCollectionStarted(view, viewModel)
+ isCollecting = true
+
+ launch {
+ visibilityState.collect { state ->
+ when (state) {
+ STATE_ICON -> {
+ mobileGroupView.visibility = VISIBLE
+ dotView.visibility = GONE
+ }
+ STATE_DOT -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = VISIBLE
+ }
+ STATE_HIDDEN -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = INVISIBLE
+ }
+ }
+ }
}
- }
- // Set the icon for the triangle
- launch {
- viewModel.icon.distinctUntilChanged().collect { icon ->
- viewModel.verboseLogger?.logBinderReceivedSignalIcon(
- view,
- viewModel.subscriptionId,
- icon,
- )
- mobileDrawable.level = icon.toSignalDrawableState()
+ // Set the icon for the triangle
+ launch {
+ viewModel.icon.distinctUntilChanged().collect { icon ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ icon,
+ )
+ mobileDrawable.level = icon.toSignalDrawableState()
+ }
}
- }
- launch {
- viewModel.contentDescription.distinctUntilChanged().collect {
- ContentDescriptionViewBinder.bind(it, view)
+ launch {
+ viewModel.contentDescription.distinctUntilChanged().collect {
+ ContentDescriptionViewBinder.bind(it, view)
+ }
}
- }
- // Set the network type icon
- launch {
- viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
- viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
- view,
- viewModel.subscriptionId,
- dataTypeId,
- )
- dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
- networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ // Set the network type icon
+ launch {
+ viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+ view,
+ viewModel.subscriptionId,
+ dataTypeId,
+ )
+ dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+ networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ }
}
- }
- // Set the roaming indicator
- launch {
- viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
- roamingView.isVisible = isRoaming
- roamingSpace.isVisible = isRoaming
+ // Set the roaming indicator
+ launch {
+ viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+ roamingView.isVisible = isRoaming
+ roamingSpace.isVisible = isRoaming
+ }
}
- }
- // Set the activity indicators
- launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+ // Set the activity indicators
+ launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
- launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+ launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
- launch {
- viewModel.activityContainerVisible.collect { activityContainer.isVisible = it }
- }
+ launch {
+ viewModel.activityContainerVisible.collect {
+ activityContainer.isVisible = it
+ }
+ }
- // Set the tint
- launch {
- iconTint.collect { tint ->
- val tintList = ColorStateList.valueOf(tint)
- iconView.imageTintList = tintList
- networkTypeView.imageTintList = tintList
- roamingView.imageTintList = tintList
- activityIn.imageTintList = tintList
- activityOut.imageTintList = tintList
- dotView.setDecorColor(tint)
+ // Set the tint
+ launch {
+ iconTint.collect { tint ->
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ networkTypeView.imageTintList = tintList
+ roamingView.imageTintList = tintList
+ activityIn.imageTintList = tintList
+ activityOut.imageTintList = tintList
+ dotView.setDecorColor(tint)
+ }
}
- }
- launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+ launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
- try {
- awaitCancellation()
- } finally {
- isCollecting = false
- logger.logCollectionStopped(view, viewModel)
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ logger.logCollectionStopped(view, viewModel)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 120ba4eba094..b6f167704cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -65,7 +65,7 @@ constructor(
// [DefaultConnectionModel]
private val wifiIconFlow: Flow<InternetTileModel> =
wifiInteractor.wifiNetwork.flatMapLatest {
- val wifiIcon = WifiIcon.fromModel(it, context)
+ val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
flowOf(
InternetTileModel.Active(
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 8156500528a7..668c5b3c4b78 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
@@ -65,8 +65,18 @@ sealed interface WifiIcon : Diffable<WifiIcon> {
@VisibleForTesting
internal val NO_INTERNET = R.string.data_connection_no_internet
- /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */
- fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon =
+ /**
+ * Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon].
+ *
+ * @param showHotspotInfo true if the wifi icon should represent the hotspot device (if it
+ * exists) and false if the wifi icon should only ever show the wifi level and *not* the
+ * hotspot device.
+ */
+ fun fromModel(
+ model: WifiNetworkModel,
+ context: Context,
+ showHotspotInfo: Boolean,
+ ): WifiIcon =
when (model) {
is WifiNetworkModel.Unavailable -> Hidden
is WifiNetworkModel.Invalid -> Hidden
@@ -82,22 +92,50 @@ sealed interface WifiIcon : Diffable<WifiIcon> {
)
is WifiNetworkModel.Active -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
- when {
- model.isValidated ->
- Visible(
- WifiIcons.WIFI_FULL_ICONS[model.level],
- ContentDescription.Loaded(levelDesc),
- )
- else ->
- Visible(
- WifiIcons.WIFI_NO_INTERNET_ICONS[model.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- ),
- )
- }
+ val contentDescription =
+ ContentDescription.Loaded(
+ if (model.isValidated) {
+ (levelDesc)
+ } else {
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ }
+ )
+ Visible(model.toIcon(showHotspotInfo), contentDescription)
+ }
+ }
+
+ @DrawableRes
+ private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
+ return if (!showHotspotInfo) {
+ this.toBasicIcon()
+ } 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
}
}
+ }
+
+ @DrawableRes
+ private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+ return if (this.isValidated) {
+ WifiIcons.WIFI_FULL_ICONS[this.level]
+ } else {
+ WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 27ac7b9be6e6..d099c8eb1c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -66,11 +66,6 @@ constructor(
@Application private val scope: CoroutineScope,
wifiConstants: WifiConstants,
) : WifiViewModelCommon {
- /** Returns the icon to use based on the given network. */
- private fun WifiNetworkModel.icon(): WifiIcon {
- return WifiIcon.fromModel(this, context)
- }
-
override val wifiIcon: StateFlow<WifiIcon> =
combine(
interactor.isEnabled,
@@ -82,7 +77,8 @@ constructor(
return@combine WifiIcon.Hidden
}
- val icon = wifiNetwork.icon()
+ // Don't show any hotspot info in the status bar.
+ val icon = WifiIcon.fromModel(wifiNetwork, context, showHotspotInfo = false)
return@combine when {
isDefault -> icon
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index b100336b602f..f9830b124827 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -71,6 +71,7 @@ import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -163,6 +164,204 @@ public class AppOpsControllerTest extends SysuiTestCase {
}
@Test
+ public void startListening_fetchesCurrentActive_none() {
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of());
+
+ mController.setListening(true);
+
+ assertThat(mController.getActiveAppOps()).isEmpty();
+ }
+
+ /** Regression test for b/294104969. */
+ @Test
+ public void startListening_fetchesCurrentActive_oneActive() {
+ AppOpsManager.PackageOps packageOps = createPackageOp(
+ "package.test",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the op
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.test");
+ assertThat(first.getUid()).isEqualTo(2);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multiplePackages() {
+ AppOpsManager.PackageOps packageOps1 = createPackageOp(
+ "package.one",
+ /* packageUid= */ 1,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps2 = createPackageOp(
+ "package.two",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ false);
+ AppOpsManager.PackageOps packageOps3 = createPackageOp(
+ "package.three",
+ /* packageUid= */ 3,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+
+ AppOpItem item0 = list.get(0);
+ assertThat(item0.getPackageName()).isEqualTo("package.one");
+ assertThat(item0.getUid()).isEqualTo(1);
+ assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+
+ AppOpItem item1 = list.get(1);
+ assertThat(item1.getPackageName()).isEqualTo("package.three");
+ assertThat(item1.getUid()).isEqualTo(3);
+ assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multipleEntries() {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getUid()).thenReturn(1);
+ when(packageOps.getPackageName()).thenReturn("package.one");
+
+ // Entry 1
+ AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
+ when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
+ AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed1.isRunning()).thenReturn(true);
+ when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
+ // Entry 2
+ AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
+ when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
+ AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed2.isRunning()).thenReturn(true);
+ when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
+ // Entry 3
+ AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
+ when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
+ AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed3.isRunning()).thenReturn(false);
+ when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
+
+ when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.one");
+ assertThat(first.getUid()).isEqualTo(1);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
+
+ AppOpItem second = list.get(1);
+ assertThat(second.getPackageName()).isEqualTo("package.one");
+ assertThat(second.getUid()).isEqualTo(1);
+ assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multipleAttributes() {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getUid()).thenReturn(1);
+ when(packageOps.getPackageName()).thenReturn("package.one");
+ AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+ when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
+
+ AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed1.isRunning()).thenReturn(false);
+ AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed2.isRunning()).thenReturn(true);
+ AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed3.isRunning()).thenReturn(true);
+ when(entry.getAttributedOpEntries()).thenReturn(
+ Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
+
+ when(packageOps.getOps()).thenReturn(List.of(entry));
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ // Multiple attributes get merged into one entry in the active ops
+ assertEquals(1, list.size());
+
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.one");
+ assertThat(first.getUid()).isEqualTo(1);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
+ }
+
+ /** Regression test for b/294104969. */
+ @Test
+ public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
+ AppOpsManager.PackageOps packageOps1 = createPackageOp(
+ "package.one",
+ /* packageUid= */ 1,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps2 = createPackageOp(
+ "package.two",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_RECORD_AUDIO,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps3 = createPackageOp(
+ "package.three",
+ /* packageUid= */ 3,
+ AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+ // WHEN we start listening
+ mController.addCallback(
+ new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
+ mCallback);
+ mTestableLooper.processAllMessages();
+
+ // THEN the callback is notified of the current active ops it cares about
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION,
+ /* uid= */ 1,
+ "package.one",
+ true);
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_RECORD_AUDIO,
+ /* uid= */ 2,
+ "package.two",
+ true);
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+ /* uid= */ 3,
+ "package.three",
+ true);
+ }
+
+ @Test
public void addCallback_includedCode() {
mController.addCallback(
new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
@@ -772,6 +971,22 @@ public class AppOpsControllerTest extends SysuiTestCase {
assertFalse(list.get(1).isDisabled());
}
+ private AppOpsManager.PackageOps createPackageOp(
+ String packageName, int packageUid, String opStr, boolean isRunning) {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getPackageName()).thenReturn(packageName);
+ when(packageOps.getUid()).thenReturn(packageUid);
+ AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+ when(entry.getOpStr()).thenReturn(opStr);
+ AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed.isRunning()).thenReturn(isRunning);
+
+ when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
+ when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
+
+ return packageOps;
+ }
+
private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) {
mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 0b27bc9c7dbd..54f66dc957d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import dagger.Lazy
import java.util.Optional
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -173,10 +172,9 @@ class ControlsComponentTest : SysuiTestCase() {
private fun setupComponent(enabled: Boolean): ControlsComponent {
return ControlsComponent(
enabled,
- mContext,
- Lazy { controller },
- Lazy { uiController },
- Lazy { listingController },
+ { controller },
+ { uiController },
+ { listingController },
lockPatternUtils,
keyguardStateController,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 338182a3e304..b9ee19b9eaf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -36,6 +36,8 @@ import android.os.IThermalService;
import android.os.PowerManager;
import android.os.Temperature;
import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -50,20 +52,17 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -93,15 +92,12 @@ public class PowerUITest extends SysuiTestCase {
private IThermalEventListener mSkinThermalEventListener;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private CommandQueue mCommandQueue;
- @Mock private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private IVrManager mVrManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mCentralSurfacesOptionalLazy.get()).thenReturn(Optional.of(mCentralSurfaces));
-
createPowerUi();
mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
@@ -143,6 +139,23 @@ public class PowerUITest extends SysuiTestCase {
}
@Test
+ public void testSkinWarning_throttlingEmergency_butVrMode() throws Exception {
+ mPowerUI.start();
+
+ ArgumentCaptor<IVrStateCallbacks> vrCallback =
+ ArgumentCaptor.forClass(IVrStateCallbacks.class);
+ verify(mVrManager).registerListener(vrCallback.capture());
+
+ vrCallback.getValue().onVrStateChanged(true);
+ final Temperature temp = getEmergencyStatusTemp(Temperature.TYPE_SKIN, "skin2");
+ mSkinThermalEventListener.notifyThrottling(temp);
+
+ TestableLooper.get(this).processAllMessages();
+ // don't show skin high temperature warning when in VR mode
+ verify(mMockWarnings, never()).showHighTemperatureWarning();
+ }
+
+ @Test
public void testUsbAlarm_throttlingCritical() throws Exception {
mPowerUI.start();
@@ -683,8 +696,14 @@ public class PowerUITest extends SysuiTestCase {
private void createPowerUi() {
mPowerUI = new PowerUI(
- mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
- mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
+ mContext,
+ mBroadcastDispatcher,
+ mCommandQueue,
+ mVrManager,
+ mMockWarnings,
+ mEnhancedEstimates,
+ mWakefulnessLifecycle,
+ mPowerManager,
mUserTracker);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 5fa6b3a15d35..e7056c7b0b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -58,6 +58,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel;
import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.kotlin.FlowProviderKt;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import com.android.systemui.utils.os.FakeHandler;
@@ -72,6 +73,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import kotlinx.coroutines.flow.MutableStateFlow;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -105,7 +108,8 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
private ShadeCarrier mShadeCarrier3;
private TestableLooper mTestableLooper;
@Mock
- private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
+ private ShadeCarrierGroupController.OnSingleCarrierChangedListener
+ mOnSingleCarrierChangedListener;
@Mock
private MobileUiAdapter mMobileUiAdapter;
@Mock
@@ -119,6 +123,9 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
@Mock
private StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final MutableStateFlow<Boolean> mIsVisibleFlow =
+ FlowProviderKt.getMutableStateFlow(true);
+
private FakeSlotIndexResolver mSlotIndexResolver;
private ClickListenerTextView mNoCarrierTextView;
@@ -170,7 +177,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
mMobileUiAdapter,
mMobileContextProvider,
mStatusBarPipelineFlags
- )
+ )
.setShadeCarrierGroup(mShadeCarrierGroup)
.build();
@@ -181,6 +188,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
when(mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()).thenReturn(true);
when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
when(mMobileIconsViewModel.getLogger()).thenReturn(mMobileViewLogger);
+ when(mShadeCarrierGroupMobileIconViewModel.isVisible()).thenReturn(mIsVisibleFlow);
when(mMobileIconsViewModel.viewModelForSub(anyInt(), any()))
.thenReturn(mShadeCarrierGroupMobileIconViewModel);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 6be2fa586f00..4fcccf887e58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -93,9 +93,6 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
fakeFeatureFlags
)
- // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
- systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
-
// StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(android.util.Pair(10, 10))
@@ -156,6 +153,21 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
assertEquals(0f, batteryChip.view.alpha)
}
+ /** Regression test for b/294104969. */
+ @Test
+ fun testPrivacyStatusEvent_beforeSystemUptime_stillDisplayed() = runTest {
+ initializeSystemStatusAnimationScheduler(testScope = this, advancePastMinUptime = false)
+
+ // WHEN the uptime hasn't quite passed the minimum required uptime...
+ systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME / 2)
+
+ // BUT the event is a privacy event
+ createAndScheduleFakePrivacyEvent()
+
+ // THEN the privacy event still happens
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+ }
+
@Test
fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
// Instantiate class under test with TestScope from runTest
@@ -568,7 +580,10 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
return batteryChip
}
- private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+ private fun initializeSystemStatusAnimationScheduler(
+ testScope: TestScope,
+ advancePastMinUptime: Boolean = true,
+ ) {
systemStatusAnimationScheduler =
SystemStatusAnimationSchedulerImpl(
systemEventCoordinator,
@@ -581,5 +596,10 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
)
// add a mock listener
systemStatusAnimationScheduler.addCallback(listener)
+
+ if (advancePastMinUptime) {
+ // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+ systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+ }
}
}
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 8150a313abe6..6624ec2ece77 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
@@ -23,6 +23,7 @@ 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.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -41,7 +42,6 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepo
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -144,17 +144,112 @@ class InternetTileViewModelTest : SysuiTestCase() {
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(networkModel)
- // Type is [Visible] since that is the only model that stores a resId
- val expectedIcon: WifiIcon.Visible =
- WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible
-
assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
assertThat(latest?.secondaryLabel).isNull()
- assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res))
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
assertThat(latest?.iconId).isNull()
}
@Test
+ fun wifiDefaultAndActive_hotspotNone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotTablet() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotLaptop() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotWatch() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotAuto() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotPhone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotUnknown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotInvalid() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
fun wifiDefaultAndNotActive_noNetworksAvailable() =
testScope.runTest {
val latest by collectLastValue(underTest.tileModel)
@@ -237,6 +332,20 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon).isNull()
}
+ private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = hotspot,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ }
+
companion object {
const val SUB_1_ID = 1
}
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 5aacc6626eb7..a520f6c109cc 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
@@ -22,6 +22,7 @@ import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -117,6 +118,27 @@ class WifiViewModelTest : SysuiTestCase() {
}
@Test
+ fun wifiIcon_validHotspot_hotspotIconNotShown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiIcon)
+
+ // Even WHEN the network has a valid hotspot type
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = true,
+ level = 1,
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
+ )
+ )
+
+ // THEN the hotspot icon is not used for the status bar icon, and the typical wifi icon
+ // is used instead
+ assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
+ assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+ }
+
+ @Test
fun activity_showActivityConfigFalse_outputsFalse() =
testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
new file mode 100644
index 000000000000..274acc9a8c23
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Wrapper for flow constructors that can be retrieved from java tests */
+fun <T> getMutableStateFlow(value: T): MutableStateFlow<T> = MutableStateFlow(value)
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index b573800fde18..3b02be5d6342 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1750,12 +1750,8 @@ public class UserBackupManagerService {
synchronized (mClearDataLock) {
mClearingData = true;
- try {
- mActivityManager.clearApplicationUserData(packageName, keepSystemState, observer,
- mUserId);
- } catch (RemoteException e) {
- // can't happen because the activity manager is in this process
- }
+ mActivityManagerInternal.clearApplicationUserData(packageName, keepSystemState,
+ /*isRestore=*/ true, observer, mUserId);
// Only wait 30 seconds for the clear data to happen.
long timeoutMark = System.currentTimeMillis() + CLEAR_DATA_TIMEOUT_INTERVAL;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5773e204938e..bf9cdbec7b0c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3531,6 +3531,12 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public boolean clearApplicationUserData(final String packageName, boolean keepState,
final IPackageDataObserver observer, int userId) {
+ return clearApplicationUserData(packageName, keepState, /*isRestore=*/ false, observer,
+ userId);
+ }
+
+ private boolean clearApplicationUserData(final String packageName, boolean keepState,
+ boolean isRestore, final IPackageDataObserver observer, int userId) {
enforceNotIsolatedCaller("clearApplicationUserData");
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
@@ -3625,6 +3631,9 @@ public class ActivityManagerService extends IActivityManager.Stub
intent.putExtra(Intent.EXTRA_UID,
(appInfo != null) ? appInfo.uid : INVALID_UID);
intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+ if (isRestore) {
+ intent.putExtra(Intent.EXTRA_IS_RESTORE, true);
+ }
if (isInstantApp) {
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
}
@@ -19014,6 +19023,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return mAppProfiler.mCachedAppsWatermarkData.getCachedAppsHighWatermarkStats(
atomTag, resetAfterPull);
}
+
+ @Override
+ public boolean clearApplicationUserData(final String packageName, boolean keepState,
+ boolean isRestore, final IPackageDataObserver observer, int userId) {
+ return ActivityManagerService.this.clearApplicationUserData(packageName, keepState,
+ isRestore, observer, userId);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 0fc8ababbafb..f1c74f0b9fb2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -78,7 +78,8 @@ public class BiometricNotificationUtils {
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
- FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+ Notification.CATEGORY_SYSTEM, FACE_RE_ENROLL_NOTIFICATION_TAG,
+ Notification.VISIBILITY_SECRET);
}
/**
@@ -101,7 +102,8 @@ public class BiometricNotificationUtils {
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
- FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+ Notification.CATEGORY_RECOMMENDATION, FACE_ENROLL_NOTIFICATION_TAG,
+ Notification.VISIBILITY_PUBLIC);
}
/**
@@ -124,8 +126,8 @@ public class BiometricNotificationUtils {
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent,
- FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
- Notification.VISIBILITY_PUBLIC);
+ Notification.CATEGORY_RECOMMENDATION, FINGERPRINT_ENROLL_CHANNEL,
+ FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
}
/**
@@ -159,13 +161,13 @@ public class BiometricNotificationUtils {
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent,
- FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
- Notification.VISIBILITY_SECRET);
+ Notification.CATEGORY_SYSTEM, FINGERPRINT_BAD_CALIBRATION_CHANNEL,
+ BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
}
private static void showNotificationHelper(Context context, String name, String title,
- String content, PendingIntent pendingIntent, String channelName,
- String notificationTag, int visibility) {
+ String content, PendingIntent pendingIntent, String category,
+ String channelName, String notificationTag, int visibility) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -178,7 +180,7 @@ public class BiometricNotificationUtils {
.setOnlyAlertOnce(true)
.setLocalOnly(true)
.setAutoCancel(true)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ .setCategory(category)
.setContentIntent(pendingIntent)
.setVisibility(visibility)
.build();
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 39b8bfd51e3f..36adea7e0112 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -73,6 +73,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetdUtils.ModifyOperation;
import com.android.net.module.util.PermissionUtils;
@@ -782,7 +783,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public boolean getIpForwardingEnabled() throws IllegalStateException{
PermissionUtils.enforceNetworkStackPermission(mContext);
-
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#getIpForwardingEnabled not supported in V+");
+ }
try {
return mNetdService.ipfwdEnabled();
} catch (RemoteException | ServiceSpecificException e) {
@@ -793,7 +797,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void setIpForwardingEnabled(boolean enable) {
PermissionUtils.enforceNetworkStackPermission(mContext);
- try {
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#setIpForwardingEnabled not supported in V+");
+ } try {
if (enable) {
mNetdService.ipfwdEnableForwarding("tethering");
} else {
@@ -807,6 +814,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void startTethering(String[] dhcpRange) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#startTethering not supported in V+");
+ }
try {
NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
} catch (RemoteException | ServiceSpecificException e) {
@@ -817,6 +827,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void stopTethering() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#stopTethering not supported in V+");
+ }
try {
mNetdService.tetherStop();
} catch (RemoteException | ServiceSpecificException e) {
@@ -827,6 +840,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public boolean isTetheringStarted() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+");
+ }
try {
return mNetdService.tetherIsEnabled();
} catch (RemoteException | ServiceSpecificException e) {
@@ -837,6 +853,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void tetherInterface(String iface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+");
+ }
try {
final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
@@ -849,6 +868,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void untetherInterface(String iface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+");
+ }
try {
NetdUtils.untetherInterface(mNetdService, iface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -859,6 +881,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public String[] listTetheredInterfaces() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#listTetheredInterfaces not supported in V+");
+ }
try {
return mNetdService.tetherInterfaceList();
} catch (RemoteException | ServiceSpecificException e) {
@@ -869,6 +895,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void enableNat(String internalInterface, String externalInterface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#enableNat not supported in V+");
+ }
try {
mNetdService.tetherAddForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -879,6 +908,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
@Override
public void disableNat(String internalInterface, String externalInterface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#disableNat not supported in V+");
+ }
try {
mNetdService.tetherRemoveForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e49074573ed3..a700d3235ea4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1101,8 +1101,11 @@ public class ZenModeHelper {
.allowAlarms(true)
.allowMedia(true)
.build());
- } else {
+ } else if (rule.zenPolicy != null) {
policy.apply(rule.zenPolicy);
+ } else {
+ // active rule with no specified policy inherits the default settings
+ policy.apply(mConfig.toZenPolicy());
}
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index a988821fe915..b8feb4d044bf 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.CONTROL_KEYGUARD;
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -63,6 +64,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -443,7 +445,7 @@ final class DeletePackageHelper {
// semantics than normal for uninstalling system apps.
final boolean clearPackageStateAndReturn;
synchronized (mPm.mLock) {
- markPackageUninstalledForUserLPw(ps, user);
+ markPackageUninstalledForUserLPw(ps, user, flags);
if (!systemApp) {
// Do not uninstall the APK if an app should be cached
boolean keepUninstalledPackage =
@@ -547,7 +549,7 @@ final class DeletePackageHelper {
}
@GuardedBy("mPm.mLock")
- private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user) {
+ private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user, int flags) {
final int[] userIds = (user == null || user.getIdentifier() == UserHandle.USER_ALL)
? mUserManagerInternal.getUserIds()
: new int[] {user.getIdentifier()};
@@ -556,6 +558,12 @@ final class DeletePackageHelper {
Slog.d(TAG, "Marking package:" + ps.getPackageName()
+ " uninstalled for user:" + nextUserId);
}
+ // Preserve ArchiveState if this is not a full uninstall
+ ArchiveState archiveState =
+ (flags & DELETE_KEEP_DATA) == 0
+ ? null
+ : ps.getUserStateOrDefault(nextUserId).getArchiveState();
+
ps.setUserState(nextUserId,
ps.getCeDataInode(nextUserId),
COMPONENT_ENABLED_STATE_DEFAULT,
@@ -576,7 +584,7 @@ final class DeletePackageHelper {
null /*splashScreenTheme*/,
0 /*firstInstallTime*/,
PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
- null /*archiveState*/);
+ archiveState);
}
mPm.mSettings.writeKernelMappingLPr(ps);
}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index f3ea42e0e0da..2a00a442542d 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -619,7 +619,7 @@ final class DumpHelper {
pw.println(" --checkin: dump for a checkin");
pw.println(" -f: print details of intent filters");
pw.println(" -h: print this help");
- pw.println(" ---proto: dump data to proto");
+ pw.println(" --proto: dump data to proto");
pw.println(" --all-components: include all component names in package dump");
pw.println(" --include-apex: includes the apex packages in package dump");
pw.println(" cmd may be one of:");
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 3e18387e81b9..f8313e7d18ff 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1582,7 +1582,8 @@ final class InstallPackageHelper {
final PackageFreezer freezer =
freezePackageForInstall(pkgName, UserHandle.USER_ALL, installFlags,
- "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED);
+ "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
+
boolean shouldCloseFreezerBeforeReturn = true;
try {
final PackageState oldPackageState;
@@ -2032,11 +2033,11 @@ final class InstallPackageHelper {
}
private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
- String killReason, int exitInfoReason) {
+ String killReason, int exitInfoReason, InstallRequest request) {
if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
- return new PackageFreezer(mPm);
+ return new PackageFreezer(mPm, request);
} else {
- return mPm.freezePackage(packageName, userId, killReason, exitInfoReason);
+ return mPm.freezePackage(packageName, userId, killReason, exitInfoReason, request);
}
}
@@ -3207,7 +3208,7 @@ final class InstallPackageHelper {
try (PackageFreezer freezer =
mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
"setEnabledSetting",
- ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+ ApplicationExitInfo.REASON_PACKAGE_UPDATED, null /* request */)) {
pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/);
mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
synchronized (mPm.mLock) {
@@ -3231,7 +3232,8 @@ final class InstallPackageHelper {
try (PackageFreezer freezer =
mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
"setEnabledSetting",
- ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+ ApplicationExitInfo.REASON_PACKAGE_UPDATED,
+ null /* request */)) {
synchronized (mPm.mLock) {
// NOTE: Ensure the system package is enabled; even for a compressed stub.
// If we don't, installing the system package fails during scan
@@ -4291,7 +4293,8 @@ final class InstallPackageHelper {
+ " name: " + pkgSetting.getPackageName());
try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
parsedPackage.getPackageName(), UserHandle.USER_ALL,
- "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER)) {
+ "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER,
+ null /* request */)) {
DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
mPm.mUserManager.getUserIds(), 0, null, false);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 6fc14e814525..6c265314c0cc 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -831,4 +831,16 @@ final class InstallRequest {
}
}
}
+
+ public void onFreezeStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
+ }
+ }
+
+ public void onFreezeCompleted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 30a23bfd0641..fe6a8a1778a2 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -543,7 +543,6 @@ class InstallingSession {
mInstallPackageHelper.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
- request.onInstallCompleted();
doPostInstall(request);
}
}
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 01c27348db94..148e0df7cf64 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -147,7 +147,8 @@ public final class MovePackageHelper {
final PackageFreezer freezer;
synchronized (mPm.mLock) {
freezer = mPm.freezePackage(packageName, UserHandle.USER_ALL,
- "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED);
+ "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED,
+ null /* request */);
}
final Bundle extras = new Bundle();
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 841b66e2de18..7c5615771607 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import dalvik.system.CloseGuard;
@@ -29,6 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
* app code/data to prevent the app from running while you're working.
*/
final class PackageFreezer implements AutoCloseable {
+ @Nullable private InstallRequest mInstallRequest;
+
private final String mPackageName;
private final AtomicBoolean mClosed = new AtomicBoolean();
@@ -43,18 +46,29 @@ final class PackageFreezer implements AutoCloseable {
* {@link PackageManager#INSTALL_DONT_KILL_APP} or
* {@link PackageManager#DELETE_DONT_KILL_APP}.
*/
- PackageFreezer(PackageManagerService pm) {
+
+ PackageFreezer(PackageManagerService pm, @Nullable InstallRequest request) {
mPm = pm;
mPackageName = null;
mClosed.set(true);
mCloseGuard.open("close");
+ mInstallRequest = request;
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeStarted();
+ }
}
PackageFreezer(String packageName, int userId, String killReason,
- PackageManagerService pm, int exitInfoReason) {
+ PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) {
mPm = pm;
mPackageName = packageName;
+ mInstallRequest = request;
final PackageSetting ps;
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeStarted();
+ }
synchronized (mPm.mLock) {
final int refCounts = mPm.mFrozenPackages
.getOrDefault(mPackageName, 0 /* defaultValue */) + 1;
@@ -92,5 +106,10 @@ final class PackageFreezer implements AutoCloseable {
}
}
}
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeCompleted();
+ mInstallRequest = null;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 83d2f6ae0e40..b4ca477ddfec 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -93,6 +93,7 @@ final class PackageHandler extends Handler {
mPm.mRunningInstalls.delete(msg.arg1);
request.closeFreezer();
+ request.onInstallCompleted();
request.runPostInstallRunnable();
if (!request.isInstallExistingForUser()) {
mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ac7842948639..c0c3ec421979 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4294,16 +4294,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
public PackageFreezer freezePackage(String packageName, int userId, String killReason,
- int exitInfoReason) {
- return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason);
+ int exitInfoReason, InstallRequest request) {
+ return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request);
}
public PackageFreezer freezePackageForDelete(String packageName, int userId, int deleteFlags,
String killReason, int exitInfoReason) {
if ((deleteFlags & PackageManager.DELETE_DONT_KILL_APP) != 0) {
- return new PackageFreezer(this);
+ return new PackageFreezer(this, null /* request */);
} else {
- return freezePackage(packageName, userId, killReason, exitInfoReason);
+ return freezePackage(packageName, userId, killReason, exitInfoReason,
+ null /* request */);
}
}
@@ -4632,7 +4633,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
try (PackageFreezer ignored =
freezePackage(packageName, UserHandle.USER_ALL,
"clearApplicationProfileData",
- ApplicationExitInfo.REASON_OTHER)) {
+ ApplicationExitInfo.REASON_OTHER, null /* request */)) {
synchronized (mInstallLock) {
mAppDataHelper.clearAppProfilesLIF(pkg);
}
@@ -4675,7 +4676,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final boolean succeeded;
try (PackageFreezer freezer = freezePackage(packageName, UserHandle.USER_ALL,
"clearApplicationUserData",
- ApplicationExitInfo.REASON_USER_REQUESTED)) {
+ ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */)) {
synchronized (mInstallLock) {
succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
userId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8bdbe04ec4e6..2304a2338c46 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1565,6 +1565,17 @@ class PackageManagerShellCommand extends ShellCommand {
private int doRunInstall(final InstallParams params) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+ int requestUserId = params.userId;
+ if (requestUserId != UserHandle.USER_ALL && requestUserId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(requestUserId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + requestUserId + " doesn't exist]");
+ return 1;
+ }
+ }
+
final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
final boolean isApex =
(params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
@@ -2319,6 +2330,15 @@ class PackageManagerShellCommand extends ShellCommand {
break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
break;
case "--versionCode":
versionCode = Long.parseLong(getNextArgRequired());
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 2d58fe5a1678..c1580c4b1cb9 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -51,13 +51,15 @@ final class PackageMetrics {
public static final int STEP_RECONCILE = 3;
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
+ public static final int STEP_FREEZE_INSTALL = 6;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
STEP_SCAN,
STEP_RECONCILE,
STEP_COMMIT,
- STEP_DEXOPT
+ STEP_DEXOPT,
+ STEP_FREEZE_INSTALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
@@ -109,10 +111,17 @@ final class PackageMetrics {
long versionCode = 0, apksSize = 0;
if (success) {
- final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
- if (ps != null) {
- versionCode = ps.getVersionCode();
- apksSize = getApksSize(ps.getPath());
+ // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because
+ // the scan result is null for installExistingPackageAsUser(). Because it's installing
+ // a package that's already existing, there's no scanning or parsing involved
+ try {
+ final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+ if (ps != null) {
+ versionCode = ps.getVersionCode();
+ apksSize = getApksSize(ps.getPath());
+ }
+ } catch (IllegalStateException e) {
+ // no-op
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index d32eb22afb61..e9c6aabccbf2 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -363,14 +363,14 @@ class ShortcutUser {
private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
boolean forBackup) throws IOException, XmlPullParserException {
- spi.waitForBitmapSaves();
if (forBackup) {
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
+ spi.waitForBitmapSaves();
spi.saveToXml(out, forBackup);
} else {
- spi.saveShortcutPackageItem();
+ spi.scheduleSave();
}
}
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 6f0fe63cdaa8..db5b9b199b85 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -155,7 +155,8 @@ public final class StorageEventHelper extends StorageEventListener {
for (PackageStateInternal ps : packages) {
freezers.add(mPm.freezePackage(ps.getPackageName(), UserHandle.USER_ALL,
- "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER));
+ "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER,
+ null /* request */));
synchronized (mPm.mInstallLock) {
final AndroidPackage pkg;
try {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 598c1aea6d00..ddc05194c300 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2000,7 +2000,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
if (serviceInfo == null) {
- clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+ if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
+ clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null);
+ clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply);
+ } else {
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+ }
return;
}
Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2032,7 +2037,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData data = null;
synchronized (mLock) {
if (mIsLockscreenLiveWallpaperEnabled) {
- clearWallpaperLocked(callingPackage, which, userId, null);
+ clearWallpaperLocked(callingPackage, which, userId);
} else {
clearWallpaperLocked(which, userId, null);
}
@@ -2052,8 +2057,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void clearWallpaperLocked(String callingPackage, int which, int userId,
- IRemoteCallback reply) {
+ private void clearWallpaperLocked(String callingPackage, int which, int userId) {
// Might need to bring it in the first time to establish our rewrite
if (!mWallpaperMap.contains(userId)) {
@@ -2092,8 +2096,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
finalWhich = which;
}
- boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
- component, callingPackage, finalWhich, userId, reply));
+ boolean success = withCleanCallingIdentity(() -> setWallpaperComponent(
+ component, callingPackage, finalWhich, userId));
if (success) return;
} catch (IllegalArgumentException e1) {
e = e1;
@@ -2105,23 +2109,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// wallpaper.
Slog.e(TAG, "Default wallpaper component not found!", e);
withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
- if (reply != null) {
- try {
- reply.sendResult(null);
- } catch (RemoteException e1) {
- Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
- }
- }
}
+ // TODO(b/266818039) remove this version of the method
private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid());
- clearWallpaperLocked(callingPackage, which, userId, reply);
- return;
- }
-
if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
}
@@ -3293,7 +3284,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean setWallpaperComponent(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
if (mIsLockscreenLiveWallpaperEnabled) {
- return setWallpaperComponentInternal(name, callingPackage, which, userId, null);
+ return setWallpaperComponentInternal(name, callingPackage, which, userId);
} else {
setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
return true;
@@ -3301,7 +3292,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
- @SetWallpaperFlags int which, int userIdIn, IRemoteCallback reply) {
+ @SetWallpaperFlags int which, int userIdIn) {
if (DEBUG) {
Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
}
@@ -3350,7 +3341,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.d(TAG, "publish system wallpaper changed!");
}
liveSync.complete();
- if (reply != null) reply.sendResult(null);
}
};
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
index d99af7763ecb..e5fae497a280 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -88,7 +88,8 @@ class PackageFreezerTest {
@Test
fun freezePackage() {
- val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms, TEST_EXIT_REASON)
+ val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(1))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
@@ -104,9 +105,9 @@ class PackageFreezerTest {
@Test
fun freezePackage_twice() {
val freezer1 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
val freezer2 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(2))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
@@ -127,7 +128,7 @@ class PackageFreezerTest {
@Test
fun freezePackage_withoutClosing() {
var freezer: PackageFreezer? = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(1))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index e540068e6bc6..e22c10489d4d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2470,6 +2470,143 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(12345, mZenModeEventLogger.getPackageUid(4));
}
+ @Test
+ public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
+ setupZenConfig();
+
+ // When there's one automatic rule active and it doesn't specify a policy, test that the
+ // resulting consolidated policy is one that matches the default rule settings.
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable the rule
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // inspect the consolidated policy. Based on setupZenConfig() values.
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowCalls());
+ assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConsolidatedPolicy.allowCallsFrom());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges());
+ }
+
+ @Test
+ public void testUpdateConsolidatedPolicy_customPolicyOnly() {
+ setupZenConfig();
+
+ // when there's only one automatic rule active and it has a custom policy, make sure that's
+ // what the consolidated policy reflects whether or not it's stricter than what the default
+ // would specify.
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowAlarms(true) // more lenient than default
+ .allowMedia(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showBadges(true) // more lenient
+ .showPeeking(false) // more restrictive
+ .build();
+
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable the rule; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // since this is the only active rule, the consolidated policy should match the custom
+ // policy for every field specified, and take default values for unspecified things
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // custom
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.showBadges()); // custom
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom
+ }
+
+ @Test
+ public void testUpdateConsolidatedPolicy_defaultAndCustomActive() {
+ setupZenConfig();
+
+ // when there are two rules active, one inheriting the default policy and one setting its
+ // own custom policy, they should be merged to form the most restrictive combination.
+
+ // rule 1: no custom policy
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable rule 1
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // custom policy for rule 2
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowAlarms(true) // more lenient than default
+ .allowMedia(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showBadges(true) // more lenient
+ .showPeeking(false) // more restrictive
+ .build();
+
+ AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+ "test", Process.SYSTEM_UID, true);
+
+ // enable rule 2; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // now both rules should be on, and the consolidated policy should reflect the most
+ // restrictive option of each of the two
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default, unset in custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom stricter
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default, unset in custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom stricter
+ }
+
private void setupZenConfig() {
mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
mZenModeHelper.mConfig.allowAlarms = false;
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 44cf333bd5d6..085241ff971a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -63,7 +63,6 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.server.LocalServices;
@@ -1005,7 +1004,6 @@ public class VibrationThreadTest {
mVibratorProviders.get(3).getEffectSegments(vibrationId));
}
- @FlakyTest
@Test
public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
int[] vibratorIds = new int[]{1, 2};