diff options
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}; |