summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/media/framework/java/android/media/MediaCommunicationManager.java2
-rw-r--r--core/api/current.txt9
-rw-r--r--core/api/module-lib-current.txt6
-rw-r--r--core/api/system-current.txt84
-rw-r--r--core/java/android/app/ActivityOptions.java2
-rw-r--r--core/java/android/app/compat/CompatChanges.java55
-rw-r--r--core/java/android/app/prediction/AppPredictor.java2
-rw-r--r--core/java/android/app/search/SearchSession.java2
-rw-r--r--core/java/android/app/smartspace/SmartspaceSession.java2
-rw-r--r--core/java/android/content/ContentProviderClient.java4
-rw-r--r--core/java/android/content/ContentResolver.java2
-rw-r--r--core/java/android/content/Intent.java12
-rw-r--r--core/java/android/database/AbstractCursor.java2
-rw-r--r--core/java/android/database/CursorWindow.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java2
-rw-r--r--core/java/android/hardware/HardwareBuffer.java2
-rw-r--r--core/java/android/hardware/SystemSensorManager.java2
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl2
-rw-r--r--core/java/android/hardware/input/InputDeviceLightsManager.java2
-rw-r--r--core/java/android/hardware/input/InputManager.java12
-rw-r--r--core/java/android/hardware/lights/SystemLightsManager.java2
-rw-r--r--core/java/android/hardware/location/ContextHubClient.java12
-rw-r--r--core/java/android/hardware/usb/UsbDeviceConnection.java2
-rw-r--r--core/java/android/hardware/usb/UsbRequest.java2
-rw-r--r--core/java/android/net/NetworkPolicy.java29
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/games/CreateGameSessionRequest.aidl23
-rw-r--r--core/java/android/service/games/CreateGameSessionRequest.java118
-rw-r--r--core/java/android/service/games/GameService.java18
-rw-r--r--core/java/android/service/games/GameSession.java65
-rw-r--r--core/java/android/service/games/GameSessionService.java104
-rw-r--r--core/java/android/service/games/IGameSession.aidl24
-rw-r--r--core/java/android/service/games/IGameSessionService.aidl32
-rw-r--r--core/java/android/telephony/PhoneStateListener.java1
-rw-r--r--core/java/android/telephony/TelephonyCallback.java1
-rw-r--r--core/java/android/util/MemoryIntArray.java4
-rw-r--r--core/java/android/view/InputEventReceiver.java2
-rw-r--r--core/java/android/view/InputEventSender.java2
-rw-r--r--core/java/android/view/InputQueue.java2
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java11
-rw-r--r--core/java/android/view/ScrollCaptureConnection.java2
-rw-r--r--core/java/android/view/Surface.java2
-rw-r--r--core/java/android/window/TransitionInfo.java17
-rw-r--r--core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl19
-rw-r--r--core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java74
-rw-r--r--core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl19
-rw-r--r--core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java77
-rw-r--r--core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java2
-rw-r--r--core/java/com/android/internal/compat/IPlatformCompat.aidl42
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java22
-rw-r--r--core/java/com/android/internal/policy/TransitionAnimation.java32
-rw-r--r--core/jni/android_text_Hyphenator.cpp5
-rw-r--r--core/proto/android/providers/settings/global.proto2
-rw-r--r--core/res/AndroidManifest.xml25
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/values/strings.xml7
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/net/NetworkPolicyTest.kt28
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java246
-rw-r--r--data/etc/platform.xml4
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--graphics/java/android/graphics/Outline.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java114
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java105
-rw-r--r--libs/hwui/Outline.h10
-rw-r--r--libs/hwui/ProfileData.cpp1
-rw-r--r--libs/hwui/RenderProperties.h6
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp4
-rw-r--r--libs/hwui/tests/common/scenes/PathClippingAnimation.cpp153
-rw-r--r--libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp31
-rw-r--r--media/java/android/media/AudioManager.java83
-rw-r--r--media/java/android/media/MediaActionSound.java25
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java14
-rw-r--r--media/java/android/media/tv/tuner/frontend/FrontendStatus.java28
-rw-r--r--media/java/android/media/tv/tuner/frontend/ScanCallback.java3
-rw-r--r--media/jni/android_media_tv_Tuner.cpp18
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml10
-rw-r--r--packages/CompanionDeviceManager/res/layout/list_item_device.xml39
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java139
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java27
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java79
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java6
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java20
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java14
-rw-r--r--packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java306
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java4
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml (renamed from packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml)4
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml22
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml (renamed from packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml)2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml11
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml17
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml18
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml21
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.xml11
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java99
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt187
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java103
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java124
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java193
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java68
-rw-r--r--services/core/java/com/android/server/app/GameClassifier.java33
-rw-r--r--services/core/java/com/android/server/app/GameClassifierImpl.java49
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java16
-rw-r--r--services/core/java/com/android/server/app/GameServiceConnection.java102
-rw-r--r--services/core/java/com/android/server/app/GameServiceController.java172
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderConfiguration.java94
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstance.java36
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java29
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java92
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java294
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderSelector.java34
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java180
-rw-r--r--services/core/java/com/android/server/app/GameSessionRecord.java88
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java2
-rw-r--r--services/core/java/com/android/server/compat/CompatConfig.java87
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java26
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java25
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java11
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java41
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java4
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java5
-rw-r--r--services/core/java/com/android/server/pm/DumpHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java19
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java12
-rw-r--r--services/core/java/com/android/server/pm/MovePackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageFreezer.java14
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java16
-rw-r--r--services/core/java/com/android/server/pm/SELinuxMMAC.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java1
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java247
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecordInputSink.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java37
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java25
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java8
-rw-r--r--services/core/java/com/android/server/wm/Transition.java82
-rw-r--r--services/core/java/com/android/server/wm/UnknownAppVisibilityController.java4
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java24
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp6
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java3
-rw-r--r--services/proguard.flags14
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml3
-rw-r--r--services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml2
-rw-r--r--services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml3
-rw-r--r--services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java44
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java42
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java124
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java228
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java455
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java560
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java403
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt136
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING18
-rw-r--r--services/tests/servicestests/AndroidManifest.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java114
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java326
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java5
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java9
-rw-r--r--telephony/common/com/google/android/mms/util/SqliteWrapper.java49
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java8
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java50
-rw-r--r--telephony/java/android/telephony/data/DataProfile.java122
-rwxr-xr-xtools/fonts/fontchain_linter.py5
206 files changed, 6414 insertions, 2335 deletions
diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java
index 8ee9616411b8..ef5552e70e6b 100644
--- a/apex/media/framework/java/android/media/MediaCommunicationManager.java
+++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java
@@ -36,6 +36,8 @@ import android.view.KeyEvent;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
import com.android.modules.annotation.MinSdk;
import com.android.modules.utils.build.SdkLevel;
diff --git a/core/api/current.txt b/core/api/current.txt
index b52a3e45c3eb..d7d465f01b98 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -61,6 +61,7 @@ package android {
field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+ field public static final String BODY_SENSORS_BACKGROUND = "android.permission.BODY_SENSORS_BACKGROUND";
field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED";
field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
field public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
@@ -21932,6 +21933,7 @@ package android.media {
public class MediaActionSound {
ctor public MediaActionSound();
method public void load(int);
+ method public static boolean mustPlayShutterSound();
method public void play(int);
method public void release();
field public static final int FOCUS_COMPLETE = 1; // 0x1
@@ -43507,6 +43509,7 @@ package android.telephony {
field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
+ field public static final int DATA_HANDOVER_IN_PROGRESS = 5; // 0x5
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final int DATA_UNKNOWN = -1; // 0xffffffff
field public static final int DEFAULT_PORT_INDEX = 0; // 0x0
@@ -43756,6 +43759,8 @@ package android.telephony.data {
method public String getMmsProxyAddressAsString();
method public int getMmsProxyPort();
method public android.net.Uri getMmsc();
+ method public int getMtuV4();
+ method public int getMtuV6();
method public int getMvnoType();
method public int getNetworkTypeBitmask();
method public String getOperatorNumeric();
@@ -43812,10 +43817,14 @@ package android.telephony.data {
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(@Nullable String);
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyPort(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setMmsc(@Nullable android.net.Uri);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV4(int);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV6(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setMvnoType(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setOperatorNumeric(@Nullable String);
method @NonNull public android.telephony.data.ApnSetting.Builder setPassword(@Nullable String);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setPersistent(boolean);
+ method @NonNull public android.telephony.data.ApnSetting.Builder setProfileId(int);
method @NonNull public android.telephony.data.ApnSetting.Builder setProtocol(int);
method @Deprecated public android.telephony.data.ApnSetting.Builder setProxyAddress(java.net.InetAddress);
method @NonNull public android.telephony.data.ApnSetting.Builder setProxyAddress(@Nullable String);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index a51d2b165516..850b2e6433a2 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -138,6 +138,8 @@ package android.media {
public class AudioManager {
method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
+ method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio();
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean);
@@ -227,6 +229,10 @@ package android.net {
field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
}
+ public final class IpSecManager {
+ field public static final int DIRECTION_FWD = 2; // 0x2
+ }
+
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
method public int getResourceId();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6e1ab915b92c..15ed54d8726e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -240,6 +240,7 @@ package android {
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
+ field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
@@ -344,6 +345,7 @@ package android {
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+ field public static final int gameSessionService;
field public static final int hotwordDetectionService = 16844326; // 0x1010626
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844305; // 0x1010611
@@ -1398,7 +1400,9 @@ package android.app.compat {
method public static boolean isChangeEnabled(long);
method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, int);
+ method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void putAllPackageOverrides(@NonNull java.util.Map<java.lang.String,java.util.Map<java.lang.Long,android.app.compat.PackageOverride>>);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void putPackageOverrides(@NonNull String, @NonNull java.util.Map<java.lang.Long,android.app.compat.PackageOverride>);
+ method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void removeAllPackageOverrides(@NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.Long>>);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void removePackageOverrides(@NonNull String, @NonNull java.util.Set<java.lang.Long>);
}
@@ -7605,6 +7609,7 @@ package android.media.tv.tuner.frontend {
method public int getBer();
method @NonNull public int[] getBers();
method @NonNull public int[] getCodeRates();
+ method @NonNull public int[] getDvbtCellIds();
method @NonNull public int[] getExtendedModulations();
method @Deprecated public int getFreqOffset();
method public long getFreqOffsetLong();
@@ -7647,6 +7652,7 @@ package android.media.tv.tuner.frontend {
field public static final int FRONTEND_STATUS_TYPE_BERS = 23; // 0x17
field public static final int FRONTEND_STATUS_TYPE_CODERATES = 24; // 0x18
field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0
+ field public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS = 40; // 0x28
field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd
field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8
field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 18; // 0x12
@@ -7881,6 +7887,7 @@ package android.media.tv.tuner.frontend {
method public void onAtsc3PlpInfosReported(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]);
method public default void onDvbcAnnexReported(int);
method public void onDvbsStandardReported(int);
+ method public default void onDvbtCellIdsReported(@NonNull int[]);
method public void onDvbtStandardReported(int);
method public default void onFrequenciesLongReported(@NonNull long[]);
method @Deprecated public void onFrequenciesReported(@NonNull int[]);
@@ -10736,12 +10743,35 @@ package android.service.euicc {
package android.service.games {
+ public final class CreateGameSessionRequest implements android.os.Parcelable {
+ ctor public CreateGameSessionRequest(int, @NonNull String);
+ method public int describeContents();
+ method @NonNull public String getGamePackageName();
+ method public int getTaskId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.games.CreateGameSessionRequest> CREATOR;
+ }
+
public class GameService extends android.app.Service {
ctor public GameService();
method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- field public static final String SERVICE_INTERFACE = "android.service.games.GameService";
+ field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE";
+ field public static final String SERVICE_META_DATA = "android.game_service";
+ }
+
+ public abstract class GameSession {
+ ctor public GameSession();
+ method public void onCreate();
+ method public void onDestroy();
+ }
+
+ public abstract class GameSessionService extends android.app.Service {
+ ctor public GameSessionService();
+ method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest);
+ field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE";
}
}
@@ -13086,21 +13116,23 @@ package android.telephony.data {
public final class DataProfile implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public String getApn();
- method public int getAuthType();
- method public int getBearerBitmask();
+ method @Deprecated @NonNull public String getApn();
+ method @Nullable public android.telephony.data.ApnSetting getApnSetting();
+ method @Deprecated public int getAuthType();
+ method @Deprecated public int getBearerBitmask();
method @Deprecated public int getMtu();
- method public int getMtuV4();
- method public int getMtuV6();
- method @Nullable public String getPassword();
- method public int getProfileId();
- method public int getProtocolType();
- method public int getRoamingProtocolType();
- method public int getSupportedApnTypesBitmask();
+ method @Deprecated public int getMtuV4();
+ method @Deprecated public int getMtuV6();
+ method @Deprecated @Nullable public String getPassword();
+ method @Deprecated public int getProfileId();
+ method @Deprecated public int getProtocolType();
+ method @Deprecated public int getRoamingProtocolType();
+ method @Deprecated public int getSupportedApnTypesBitmask();
+ method @Nullable public android.telephony.data.TrafficDescriptor getTrafficDescriptor();
method public int getType();
- method @Nullable public String getUserName();
+ method @Deprecated @Nullable public String getUserName();
method public boolean isEnabled();
- method public boolean isPersistent();
+ method @Deprecated public boolean isPersistent();
method public boolean isPreferred();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataProfile> CREATOR;
@@ -13113,21 +13145,23 @@ package android.telephony.data {
ctor public DataProfile.Builder();
method @NonNull public android.telephony.data.DataProfile build();
method @NonNull public android.telephony.data.DataProfile.Builder enable(boolean);
- method @NonNull public android.telephony.data.DataProfile.Builder setApn(@NonNull String);
- method @NonNull public android.telephony.data.DataProfile.Builder setAuthType(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setBearerBitmask(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setApn(@NonNull String);
+ method @NonNull public android.telephony.data.DataProfile.Builder setApnSetting(@NonNull android.telephony.data.ApnSetting);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setAuthType(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setBearerBitmask(int);
method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtu(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setMtuV4(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setMtuV6(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setPassword(@NonNull String);
- method @NonNull public android.telephony.data.DataProfile.Builder setPersistent(boolean);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtuV4(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtuV6(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setPassword(@NonNull String);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setPersistent(boolean);
method @NonNull public android.telephony.data.DataProfile.Builder setPreferred(boolean);
- method @NonNull public android.telephony.data.DataProfile.Builder setProfileId(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setProtocolType(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setRoamingProtocolType(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setSupportedApnTypesBitmask(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setProfileId(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setProtocolType(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setRoamingProtocolType(int);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setSupportedApnTypesBitmask(int);
+ method @NonNull public android.telephony.data.DataProfile.Builder setTrafficDescriptor(@NonNull android.telephony.data.TrafficDescriptor);
method @NonNull public android.telephony.data.DataProfile.Builder setType(int);
- method @NonNull public android.telephony.data.DataProfile.Builder setUserName(@NonNull String);
+ method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setUserName(@NonNull String);
}
public abstract class DataService extends android.app.Service {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index d0096fdec82e..f7d5e52e3675 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -380,6 +380,8 @@ public class ActivityOptions extends ComponentOptions {
public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
/** @hide */
public static final int ANIM_REMOTE_ANIMATION = 13;
+ /** @hide */
+ public static final int ANIM_FROM_STYLE = 14;
private String mPackageName;
private Rect mLaunchBounds;
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 0d85fb9488be..d7b2ab4351a4 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -22,8 +22,11 @@ import android.annotation.SystemApi;
import android.compat.Compatibility;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import java.util.Map;
@@ -98,6 +101,31 @@ public final class CompatChanges {
}
/**
+ * Equivalent to calling {@link #putPackageOverrides(String, Map)} on each entry in {@code
+ * packageNameToOverrides}, but the state of the compat config will be updated only once
+ * instead of for each package.
+ *
+ * @param packageNameToOverrides A map from package name to a map from change ID to the
+ * override applied for that package name and change ID.
+ */
+ @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+ public static void putAllPackageOverrides(
+ @NonNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides) {
+ ArrayMap<String, CompatibilityOverrideConfig> packageNameToConfig = new ArrayMap<>();
+ for (String packageName : packageNameToOverrides.keySet()) {
+ packageNameToConfig.put(packageName,
+ new CompatibilityOverrideConfig(packageNameToOverrides.get(packageName)));
+ }
+ CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig(
+ packageNameToConfig);
+ try {
+ QUERY_CACHE.getPlatformCompatService().putAllOverridesOnReleaseBuilds(config);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Associates app compat overrides with the given package and their respective change IDs.
* This will check whether the caller is allowed to perform this operation on the given apk and
* build. Only the installer package is allowed to set overrides on a non-debuggable final
@@ -123,6 +151,33 @@ public final class CompatChanges {
}
/**
+ * Equivalent to calling {@link #removePackageOverrides(String, Set)} on each entry in {@code
+ * packageNameToOverridesToRemove}, but the state of the compat config will be updated only once
+ * instead of for each package.
+ *
+ * @param packageNameToOverridesToRemove A map from package name to a set of change IDs for
+ * which to remove overrides for that package name.
+ */
+ @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+ public static void removeAllPackageOverrides(
+ @NonNull Map<String, Set<Long>> packageNameToOverridesToRemove) {
+ ArrayMap<String, CompatibilityOverridesToRemoveConfig> packageNameToConfig =
+ new ArrayMap<>();
+ for (String packageName : packageNameToOverridesToRemove.keySet()) {
+ packageNameToConfig.put(packageName,
+ new CompatibilityOverridesToRemoveConfig(
+ packageNameToOverridesToRemove.get(packageName)));
+ }
+ CompatibilityOverridesToRemoveByPackageConfig config =
+ new CompatibilityOverridesToRemoveByPackageConfig(packageNameToConfig);
+ try {
+ QUERY_CACHE.getPlatformCompatService().removeAllOverridesOnReleaseBuilds(config);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes app compat overrides for the given package. This will check whether the caller is
* allowed to perform this operation on the given apk and build. Only the installer package is
* allowed to clear overrides on a non-debuggable final build and a non-test apk.
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index fd1b9e3bede2..db3a1921c1ba 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -105,7 +105,7 @@ public final class AppPredictor {
e.rethrowAsRuntimeException();
}
- mCloseGuard.open("close");
+ mCloseGuard.open("AppPredictor.close");
}
/**
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index a5425a20655a..2cd1d96190b0 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -106,7 +106,7 @@ public final class SearchSession implements AutoCloseable{
e.rethrowFromSystemServer();
}
- mCloseGuard.open("close");
+ mCloseGuard.open("SearchSession.close");
}
/**
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
index 9199581c3149..b523be2cc7e9 100644
--- a/core/java/android/app/smartspace/SmartspaceSession.java
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -107,7 +107,7 @@ public final class SmartspaceSession implements AutoCloseable {
e.rethrowFromSystemServer();
}
- mCloseGuard.open("close");
+ mCloseGuard.open("SmartspaceSession.close");
}
/**
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 518e7534d512..cc3c01241c66 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -109,7 +109,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
mAuthority = authority;
mStable = stable;
- mCloseGuard.open("close");
+ mCloseGuard.open("ContentProviderClient.close");
}
/**
@@ -695,7 +695,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
CursorWrapperInner(Cursor cursor) {
super(cursor);
- mCloseGuard.open("close");
+ mCloseGuard.open("CursorWrapperInner.close");
}
@Override
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 184acb1a81ef..01d231c51751 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -3858,7 +3858,7 @@ public abstract class ContentResolver implements ContentInterface {
CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) {
super(cursor);
mContentProvider = contentProvider;
- mCloseGuard.open("close");
+ mCloseGuard.open("CursorWrapperInner.close");
}
@Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1e6029f951fc..2ff29cbdcc2a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5598,7 +5598,9 @@ public class Intent implements Parcelable, Cloneable {
*
* <p>Targets provided in this way will be presented inline with all other targets provided
* by services from other apps. They will be prioritized before other service targets, but
- * after those targets provided by sources that the user has manually pinned to the front.</p>
+ * after those targets provided by sources that the user has manually pinned to the front.
+ * You can provide up to two targets on this extra (the limit of two targets
+ * starts in Android 10).</p>
*
* @see #ACTION_CHOOSER
*/
@@ -5709,9 +5711,11 @@ public class Intent implements Parcelable, Cloneable {
/**
* A Parcelable[] of {@link Intent} or
* {@link android.content.pm.LabeledIntent} objects as set with
- * {@link #putExtra(String, Parcelable[])} of additional activities to place
- * a the front of the list of choices, when shown to the user with a
- * {@link #ACTION_CHOOSER}.
+ * {@link #putExtra(String, Parcelable[])} to place
+ * at the front of the list of choices, when shown to the user with an
+ * {@link #ACTION_CHOOSER}. You can choose up to two additional activities
+ * to show before the app suggestions (the limit of two additional activities starts in
+ * Android 10).
*/
public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index cf25c3c56208..69d573f84975 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -224,7 +224,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
/* Implementation */
public AbstractCursor() {
mPos = -1;
- mCloseGuard.open("close");
+ mCloseGuard.open("AbstractCursor.close");
}
@Override
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index ccb7cf19d0b1..f13c79587a28 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -142,7 +142,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
if (mWindowPtr == 0) {
throw new AssertionError(); // Not possible, the native code won't return it.
}
- mCloseGuard.open("close");
+ mCloseGuard.open("CursorWindow.close");
}
/**
@@ -170,7 +170,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
throw new AssertionError(); // Not possible, the native code won't return it.
}
mName = nativeGetName(mWindowPtr);
- mCloseGuard.open("close");
+ mCloseGuard.open("CursorWindow.close");
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 328858b260ac..6d6ec06182d6 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -179,7 +179,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
- mCloseGuard.open("close");
+ mCloseGuard.open("SQLiteConnection.close");
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index d3ad6bb27b3c..216c9c26424d 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -218,7 +218,7 @@ public final class SQLiteConnectionPool implements Closeable {
// Mark the pool as being open for business.
mIsOpen = true;
- mCloseGuard.open("close");
+ mCloseGuard.open("SQLiteConnectionPool.close");
}
/**
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index a4a8f313e3ba..4683d252b68a 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -253,7 +253,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
NativeAllocationRegistry registry = new NativeAllocationRegistry(
loader, nGetNativeFinalizer(), bufferSize);
mCleaner = registry.registerNativeAllocation(this, mNativeObject);
- mCloseGuard.open("close");
+ mCloseGuard.open("HardwareBuffer.close");
}
@Override
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index e9fffa30ae57..282f1d343959 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -687,7 +687,7 @@ public class SystemSensorManager extends SensorManager {
new WeakReference<>(this), looper.getQueue(),
packageName, mode, manager.mContext.getOpPackageName(),
manager.mContext.getAttributionTag());
- mCloseGuard.open("dispose");
+ mCloseGuard.open("BaseEventQueue.dispose");
mManager = manager;
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 19fb845d9384..2ac194b67192 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -144,4 +144,6 @@ interface IInputManager {
void openLightSession(int deviceId, String opPkg, in IBinder token);
void closeLightSession(int deviceId, in IBinder token);
+
+ void cancelCurrentTouch();
}
diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java
index 885df7be2510..802e6dde497a 100644
--- a/core/java/android/hardware/input/InputDeviceLightsManager.java
+++ b/core/java/android/hardware/input/InputDeviceLightsManager.java
@@ -100,7 +100,7 @@ class InputDeviceLightsManager extends LightsManager {
* Instantiated by {@link LightsManager#openSession()}.
*/
private InputDeviceLightsSession() {
- mCloseGuard.open("close");
+ mCloseGuard.open("InputDeviceLightsSession.close");
}
/**
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6f0c944b76ff..6253fb90323a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1650,6 +1650,18 @@ public final class InputManager {
}
/**
+ * Cancel all ongoing pointer gestures on all displays.
+ * @hide
+ */
+ public void cancelCurrentTouch() {
+ try {
+ mIm.cancelCurrentTouch();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java
index d0df611e7842..055a7f43f9ed 100644
--- a/core/java/android/hardware/lights/SystemLightsManager.java
+++ b/core/java/android/hardware/lights/SystemLightsManager.java
@@ -145,7 +145,7 @@ public final class SystemLightsManager extends LightsManager {
*/
@RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
private SystemLightsSession() {
- mCloseGuard.open("close");
+ mCloseGuard.open("SystemLightsSession.close");
}
/**
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index a525f58371f5..9468ca2590bb 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class describing a client of the Context Hub Service.
- *
+ * <p>
* Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
* by this object are thread-safe and can be used without external synchronization.
*
@@ -69,7 +69,7 @@ public class ContextHubClient implements Closeable {
mCloseGuard = null;
} else {
mCloseGuard = CloseGuard.get();
- mCloseGuard.open("close");
+ mCloseGuard.open("ContextHubClient.close");
}
}
@@ -110,7 +110,7 @@ public class ContextHubClient implements Closeable {
* This value can be used as an identifier for the messaging channel between a
* ContextHubClient and the Context Hub. This may be used as a routing mechanism
* between various ContextHubClient objects within an application.
- *
+ * <p>
* The value returned by this method will remain the same if it is associated with
* the same client reference at the ContextHubService (for instance, the ID of a
* PendingIntent ContextHubClient will remain the same even if the local object
@@ -119,8 +119,6 @@ public class ContextHubClient implements Closeable {
* of a non-equal PendingIntent client), the ID will not be the same.
*
* @return The ID of this ContextHubClient.
- *
- * @throws IllegalStateException if the ID was not set internally.
*/
public int getId() {
if (mId == null) {
@@ -135,7 +133,7 @@ public class ContextHubClient implements Closeable {
* When this function is invoked, the messaging associated with this client is invalidated.
* All futures messages targeted for this client are dropped at the service, and the
* ContextHubClient is unregistered from the service.
- *
+ * <p>
* If this object has a PendingIntent, i.e. the object was generated via
* {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the
* Intent events corresponding to the PendingIntent will no longer be triggered.
@@ -158,7 +156,7 @@ public class ContextHubClient implements Closeable {
*
* This function returns RESULT_SUCCESS if the message has reached the HAL, but
* does not guarantee delivery of the message to the target nanoapp.
- *
+ * <p>
* Before sending the first message to your nanoapp, it's recommended that the following
* operations should be performed:
* 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 1c35cb66ada8..60d8cacd19be 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -69,7 +69,7 @@ public class UsbDeviceConnection {
boolean wasOpened = native_open(name, pfd.getFileDescriptor());
if (wasOpened) {
- mCloseGuard.open("close");
+ mCloseGuard.open("UsbDeviceConnection.close");
}
return wasOpened;
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index d1c6465d62c8..6ac5e8de8fa7 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -103,7 +103,7 @@ public class UsbRequest {
endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval());
if (wasInitialized) {
- mCloseGuard.open("close");
+ mCloseGuard.open("UsbRequest.close");
}
return wasInitialized;
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index f33a035673b8..a6ab1381aa57 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -18,8 +18,11 @@ package android.net;
import static android.net.NetworkStats.METERED_ALL;
import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
import static android.net.NetworkTemplate.MATCH_CARRIER;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import android.annotation.NonNull;
@@ -324,7 +327,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
@NonNull
private byte[] getNetworkTemplateBytesForBackup() throws IOException {
- if (!template.isPersistable()) {
+ if (!isTemplatePersistable(this.template)) {
Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
}
@@ -378,4 +381,28 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
"Restored network template contains unknown match rule " + matchRule, e);
}
}
+
+ /**
+ * Check if the template can be persisted into disk.
+ */
+ public static boolean isTemplatePersistable(@NonNull NetworkTemplate template) {
+ switch (template.getMatchRule()) {
+ case MATCH_BLUETOOTH:
+ case MATCH_ETHERNET:
+ return true;
+ case MATCH_CARRIER:
+ case MATCH_MOBILE:
+ return !template.getSubscriberIds().isEmpty();
+ case MATCH_WIFI:
+ if (Objects.equals(template.getWifiNetworkKey(), null)
+ && template.getSubscriberIds().isEmpty()) {
+ return false;
+ }
+ return true;
+ default:
+ // Don't allow persistable for unknown types or legacy types such as
+ // MATCH_MOBILE_WILDCARD, MATCH_PROXY, etc.
+ return false;
+ }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index edacffca494f..f5777ed0c8b3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5945,7 +5945,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY);
MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_SERVER);
- MOVED_TO_GLOBAL.add(Settings.Global.NSD_ON);
MOVED_TO_GLOBAL.add(Settings.Global.SET_INSTALL_LOCATION);
MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_INSTALL_LOCATION);
MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY);
@@ -12857,13 +12856,6 @@ public final class Settings {
@Readable
public static final String MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS =
"min_duration_between_recovery_steps";
- /**
- * Whether network service discovery is enabled.
- *
- * @hide
- */
- @Readable
- public static final String NSD_ON = "nsd_on";
/**
* Let user pick default install location.
diff --git a/core/java/android/service/games/CreateGameSessionRequest.aidl b/core/java/android/service/games/CreateGameSessionRequest.aidl
new file mode 100644
index 000000000000..e09cc8e55e3c
--- /dev/null
+++ b/core/java/android/service/games/CreateGameSessionRequest.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+
+/**
+ * @hide
+ */
+parcelable CreateGameSessionRequest; \ No newline at end of file
diff --git a/core/java/android/service/games/CreateGameSessionRequest.java b/core/java/android/service/games/CreateGameSessionRequest.java
new file mode 100644
index 000000000000..2129cb165b93
--- /dev/null
+++ b/core/java/android/service/games/CreateGameSessionRequest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Request object providing the context in order to create a new {@link GameSession}.
+ *
+ * This is provided to the Game Service provider via
+ * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}. It includes game
+ * (see {@link #getGamePackageName()}) that the session is associated with and a task
+ * (see {@link #getTaskId()}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CreateGameSessionRequest implements Parcelable {
+
+ @NonNull
+ public static final Parcelable.Creator<CreateGameSessionRequest> CREATOR =
+ new Parcelable.Creator<CreateGameSessionRequest>() {
+ @Override
+ public CreateGameSessionRequest createFromParcel(Parcel source) {
+ return new CreateGameSessionRequest(
+ source.readInt(),
+ source.readString8());
+ }
+
+ @Override
+ public CreateGameSessionRequest[] newArray(int size) {
+ return new CreateGameSessionRequest[0];
+ }
+ };
+
+ private final int mTaskId;
+ private final String mGamePackageName;
+
+ public CreateGameSessionRequest(int taskId, @NonNull String gamePackageName) {
+ this.mTaskId = taskId;
+ this.mGamePackageName = gamePackageName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ dest.writeString8(mGamePackageName);
+ }
+
+ /**
+ * Unique identifier for the task.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * The package name of the game associated with the session.
+ */
+ @NonNull
+ public String getGamePackageName() {
+ return mGamePackageName;
+ }
+
+ @Override
+ public String toString() {
+ return "GameSessionRequest{"
+ + "mTaskId="
+ + mTaskId
+ + ", mGamePackageName='"
+ + mGamePackageName
+ + "\'}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof CreateGameSessionRequest)) {
+ return false;
+ }
+
+ CreateGameSessionRequest that = (CreateGameSessionRequest) o;
+ return mTaskId == that.mTaskId
+ && Objects.equals(mGamePackageName, that.mGamePackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId, mGamePackageName);
+ }
+}
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index 4b440ddf5405..b79c0553460f 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -46,7 +46,7 @@ import java.util.Objects;
*/
@SystemApi
public class GameService extends Service {
- static final String TAG = "GameService";
+ private static final String TAG = "GameService";
/**
* The {@link Intent} that must be declared as handled by the service.
@@ -55,8 +55,16 @@ public class GameService extends Service {
* that other applications can not abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE =
- "android.service.games.GameService";
+ public static final String ACTION_GAME_SERVICE =
+ "android.service.games.action.GAME_SERVICE";
+
+ /**
+ * Name under which a GameService component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code>&lt;{@link
+ * android.R.styleable#GameService game-session-service}&gt;</code> tag.
+ */
+ public static final String SERVICE_META_DATA = "android.game_service";
private IGameManagerService mGameManagerService;
private final IGameService mInterface = new IGameService.Stub() {
@@ -72,6 +80,7 @@ public class GameService extends Service {
GameService::onDisconnected, GameService.this));
}
};
+
private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> {
Log.w(TAG, "System service binder died. Shutting down");
@@ -82,9 +91,10 @@ public class GameService extends Service {
@Override
@Nullable
public IBinder onBind(@Nullable Intent intent) {
- if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ if (ACTION_GAME_SERVICE.equals(intent.getAction())) {
return mInterface.asBinder();
}
+
return null;
}
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
new file mode 100644
index 000000000000..0ff08c08932b
--- /dev/null
+++ b/core/java/android/service/games/GameSession.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.annotation.SystemApi;
+import android.os.Handler;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+/**
+ * An active game session, providing a facility for the implementation to interact with the game.
+ *
+ * A Game Service provider should extend the {@link GameSession} to provide their own implementation
+ * which is then returned when a game session is created via
+ * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class GameSession {
+
+ final IGameSession mInterface = new IGameSession.Stub() {
+ @Override
+ public void destroy() {
+ Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+ GameSession::doDestroy, GameSession.this));
+ }
+ };
+
+ void doCreate() {
+ onCreate();
+ }
+
+ void doDestroy() {
+ onDestroy();
+ }
+
+ /**
+ * Initializer called when the game session is starting.
+ *
+ * This should be used perform any setup required now that the game session is created.
+ */
+ public void onCreate() {}
+
+ /**
+ * Finalizer called when the game session is ending.
+ *
+ * This should be used to perform any cleanup before the game session is destroyed.
+ */
+ public void onDestroy() {}
+}
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
new file mode 100644
index 000000000000..a2c88709b62d
--- /dev/null
+++ b/core/java/android/service/games/GameSessionService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.Objects;
+
+/**
+ * Service that hosts active game sessions.
+ *
+ * This service should be in a separate process from the {@link GameService}. This
+ * allows it to perform the heavyweight operations associated with rendering a game
+ * session overlay while games are running and release these resources (by allowing
+ * the process to be killed) when games are not running.
+ *
+ * Game Service providers must extend {@link GameSessionService} and declare the service in their
+ * Manifest. The service must require the {@link android.Manifest.permission#BIND_GAME_SERVICE} so
+ * that other application can not abuse it. This service is used to create instances of
+ * {@link GameSession} via {@link #onNewSession(CreateGameSessionRequest)} and will remain bound to
+ * so long as at least one {@link GameSession} is running.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class GameSessionService extends Service {
+ private static final String TAG = "GameSessionService";
+
+ /**
+ * The {@link Intent} action used when binding to the service.
+ * To be supported, the service must require the
+ * {@link android.Manifest.permission#BIND_GAME_SERVICE} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_GAME_SESSION_SERVICE =
+ "android.service.games.action.GAME_SESSION_SERVICE";
+
+ private final IGameSessionService mInterface = new IGameSessionService.Stub() {
+ @Override
+ public void create(CreateGameSessionRequest createGameSessionRequest,
+ AndroidFuture gameSessionFuture) {
+ Handler.getMain().post(PooledLambda.obtainRunnable(
+ GameSessionService::doCreate, GameSessionService.this,
+ createGameSessionRequest,
+ gameSessionFuture));
+ }
+ };
+
+ @Override
+ @Nullable
+ public IBinder onBind(@Nullable Intent intent) {
+ if (intent == null) {
+ return null;
+ }
+
+ if (!ACTION_GAME_SESSION_SERVICE.equals(intent.getAction())) {
+ return null;
+ }
+
+ return mInterface.asBinder();
+ }
+
+ private void doCreate(CreateGameSessionRequest createGameSessionRequest,
+ AndroidFuture<IBinder> gameSessionFuture) {
+ GameSession gameSession = onNewSession(createGameSessionRequest);
+ Objects.requireNonNull(gameSession);
+
+ gameSessionFuture.complete(gameSession.mInterface.asBinder());
+
+ gameSession.doCreate();
+ }
+
+ /**
+ * Request to create a new {@link GameSession}.
+ */
+ @NonNull
+ public abstract GameSession onNewSession(
+ @NonNull CreateGameSessionRequest createGameSessionRequest);
+}
diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl
new file mode 100644
index 000000000000..b2e9f1d21f6e
--- /dev/null
+++ b/core/java/android/service/games/IGameSession.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+/**
+ * @hide
+ */
+oneway interface IGameSession {
+ void destroy();
+}
diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl
new file mode 100644
index 000000000000..2a53ea7f8e4a
--- /dev/null
+++ b/core/java/android/service/games/IGameSessionService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.service.games.IGameSession;
+import android.service.games.CreateGameSessionRequest;
+
+import com.android.internal.infra.AndroidFuture;
+
+
+/**
+ * @hide
+ */
+oneway interface IGameSessionService {
+ void create(
+ in CreateGameSessionRequest createGameSessionRequest,
+ in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture);
+}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 5b9d69c2f9ff..e5c9adba46a9 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -743,6 +743,7 @@ public class PhoneStateListener {
* @see TelephonyManager#DATA_CONNECTING
* @see TelephonyManager#DATA_CONNECTED
* @see TelephonyManager#DATA_SUSPENDED
+ * @see TelephonyManager#DATA_HANDOVER_IN_PROGRESS
* @deprecated Use {@link TelephonyCallback.DataConnectionStateListener} instead.
*/
@Deprecated
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 3028a6d8f97a..baa9e6b184e9 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -792,6 +792,7 @@ public class TelephonyCallback {
* @see TelephonyManager#DATA_CONNECTING
* @see TelephonyManager#DATA_CONNECTED
* @see TelephonyManager#DATA_SUSPENDED
+ * @see TelephonyManager#DATA_HANDOVER_IN_PROGRESS
*/
void onDataConnectionStateChanged(@TelephonyManager.DataState int state,
@Annotation.NetworkType int networkType);
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 6da38c2c2acb..5cbbbef2cf88 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -75,7 +75,7 @@ public final class MemoryIntArray implements Parcelable, Closeable {
final String name = UUID.randomUUID().toString();
mFd = nativeCreate(name, size);
mMemoryAddr = nativeOpen(mFd, mIsOwner);
- mCloseGuard.open("close");
+ mCloseGuard.open("MemoryIntArray.close");
}
private MemoryIntArray(Parcel parcel) throws IOException {
@@ -86,7 +86,7 @@ public final class MemoryIntArray implements Parcelable, Closeable {
}
mFd = pfd.detachFd();
mMemoryAddr = nativeOpen(mFd, mIsOwner);
- mCloseGuard.open("close");
+ mCloseGuard.open("MemoryIntArray.close");
}
/**
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c9abec989cd1..a24c1f95b0c0 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -79,7 +79,7 @@ public abstract class InputEventReceiver {
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
- mCloseGuard.open("dispose");
+ mCloseGuard.open("InputEventReceiver.dispose");
}
@Override
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index d14421897860..9035f3f7a0d4 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -67,7 +67,7 @@ public abstract class InputEventSender {
mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
inputChannel, mMessageQueue);
- mCloseGuard.open("dispose");
+ mCloseGuard.open("InputEventSender.dispose");
}
@Override
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 7accb66aa3aa..ff51ebcca08e 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -52,7 +52,7 @@ public final class InputQueue {
public InputQueue() {
mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue());
- mCloseGuard.open("dispose");
+ mCloseGuard.open("InputQueue.dispose");
}
@Override
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index bf9de39124c9..b1582cf9f023 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -180,10 +180,7 @@ public class InsetsSourceConsumer {
// If we have a new leash, make sure visibility is up-to-date, even though we
// didn't want to run an animation above.
- SurfaceControl newLeash = mSourceControl.getLeash();
- if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
- applyHiddenToControl();
- }
+ applyRequestedVisibilityToControl();
// Remove the surface that owned by last control when it lost.
if (!requestedVisible && !mIsAnimationPending && lastControl == null) {
@@ -388,18 +385,20 @@ public class InsetsSourceConsumer {
}
}
- private void applyHiddenToControl() {
+ private void applyRequestedVisibilityToControl() {
if (mSourceControl == null || mSourceControl.getLeash() == null) {
return;
}
final Transaction t = mTransactionSupplier.get();
- if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible);
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
if (mRequestedVisible) {
t.show(mSourceControl.getLeash());
} else {
t.hide(mSourceControl.getLeash());
}
+ // Ensure the alpha value is aligned with the actual requested visibility.
+ t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
t.apply();
onPerceptible(mRequestedVisible);
}
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index 278b2fcc3678..cba0e970d389 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -86,7 +86,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
@Override
public ICancellationSignal startCapture(@NonNull Surface surface,
@NonNull IScrollCaptureCallbacks remote) throws RemoteException {
- mCloseGuard.open("close");
+ mCloseGuard.open("ScrollCaptureConnection.close");
if (!surface.isValid()) {
throw new RemoteException(new IllegalArgumentException("surface must be valid"));
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 904aa73f6ac4..e5ec260907df 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -755,7 +755,7 @@ public class Surface implements Parcelable {
private void setNativeObjectLocked(long ptr) {
if (mNativeObject != ptr) {
if (mNativeObject == 0 && ptr != 0) {
- mCloseGuard.open("release");
+ mCloseGuard.open("Surface.release");
} else if (mNativeObject != 0 && ptr == 0) {
mCloseGuard.close();
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 7208930c0b20..915c8fb9a6dd 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -18,6 +18,7 @@ package android.window;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
@@ -46,6 +47,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
@@ -581,6 +583,7 @@ public final class TransitionInfo implements Parcelable {
private String mPackageName;
private final Rect mTransitionBounds = new Rect();
private HardwareBuffer mThumbnail;
+ private int mAnimations;
private AnimationOptions(int type) {
mType = type;
@@ -594,6 +597,15 @@ public final class TransitionInfo implements Parcelable {
mPackageName = in.readString();
mTransitionBounds.readFromParcel(in);
mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
+ mAnimations = in.readInt();
+ }
+
+ public static AnimationOptions makeAnimOptionsFromLayoutParameters(
+ WindowManager.LayoutParams lp) {
+ AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
+ options.mPackageName = lp.packageName;
+ options.mAnimations = lp.windowAnimations;
+ return options;
}
public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
@@ -662,6 +674,10 @@ public final class TransitionInfo implements Parcelable {
return mThumbnail;
}
+ public int getAnimations() {
+ return mAnimations;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
@@ -671,6 +687,7 @@ public final class TransitionInfo implements Parcelable {
dest.writeString(mPackageName);
mTransitionBounds.writeToParcel(dest, flags);
dest.writeTypedObject(mThumbnail, flags);
+ dest.writeInt(mAnimations);
}
@NonNull
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl
new file mode 100644
index 000000000000..eed401559e37
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+parcelable CompatibilityOverridesByPackageConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java
new file mode 100644
index 000000000000..8652bb6d05e4
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parcelable containing compat config overrides by application.
+ * @hide
+ */
+public final class CompatibilityOverridesByPackageConfig implements Parcelable {
+ public final Map<String, CompatibilityOverrideConfig> packageNameToOverrides;
+
+ public CompatibilityOverridesByPackageConfig(
+ Map<String, CompatibilityOverrideConfig> packageNameToOverrides) {
+ this.packageNameToOverrides = packageNameToOverrides;
+ }
+
+ private CompatibilityOverridesByPackageConfig(Parcel in) {
+ int keyCount = in.readInt();
+ packageNameToOverrides = new HashMap<>();
+ for (int i = 0; i < keyCount; i++) {
+ String key = in.readString();
+ packageNameToOverrides.put(key,
+ CompatibilityOverrideConfig.CREATOR.createFromParcel(in));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(packageNameToOverrides.size());
+ for (String key : packageNameToOverrides.keySet()) {
+ dest.writeString(key);
+ packageNameToOverrides.get(key).writeToParcel(dest, /* flags= */ 0);
+ }
+ }
+
+ public static final Parcelable.Creator<CompatibilityOverridesByPackageConfig> CREATOR =
+ new Parcelable.Creator<CompatibilityOverridesByPackageConfig>() {
+
+ @Override
+ public CompatibilityOverridesByPackageConfig createFromParcel(Parcel in) {
+ return new CompatibilityOverridesByPackageConfig(in);
+ }
+
+ @Override
+ public CompatibilityOverridesByPackageConfig[] newArray(int size) {
+ return new CompatibilityOverridesByPackageConfig[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl
new file mode 100644
index 000000000000..b9d0cef325dd
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+parcelable CompatibilityOverridesToRemoveByPackageConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java
new file mode 100644
index 000000000000..b408d6440070
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parcelable containing compat config change IDs for which to remove overrides by application.
+ *
+ * <p>This class is separate from CompatibilityOverridesByPackageConfig since we only need change
+ * IDs.
+ * @hide
+ */
+public final class CompatibilityOverridesToRemoveByPackageConfig implements Parcelable {
+ public final Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove;
+
+ public CompatibilityOverridesToRemoveByPackageConfig(
+ Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove) {
+ this.packageNameToOverridesToRemove = packageNameToOverridesToRemove;
+ }
+
+ private CompatibilityOverridesToRemoveByPackageConfig(Parcel in) {
+ int keyCount = in.readInt();
+ packageNameToOverridesToRemove = new HashMap<>();
+ for (int i = 0; i < keyCount; i++) {
+ String key = in.readString();
+ packageNameToOverridesToRemove.put(key,
+ CompatibilityOverridesToRemoveConfig.CREATOR.createFromParcel(in));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(packageNameToOverridesToRemove.size());
+ for (String key : packageNameToOverridesToRemove.keySet()) {
+ dest.writeString(key);
+ packageNameToOverridesToRemove.get(key).writeToParcel(dest, /* flags= */ 0);
+ }
+ }
+
+ public static final Parcelable.Creator<CompatibilityOverridesToRemoveByPackageConfig> CREATOR =
+ new Parcelable.Creator<CompatibilityOverridesToRemoveByPackageConfig>() {
+
+ @Override
+ public CompatibilityOverridesToRemoveByPackageConfig createFromParcel(Parcel in) {
+ return new CompatibilityOverridesToRemoveByPackageConfig(in);
+ }
+
+ @Override
+ public CompatibilityOverridesToRemoveByPackageConfig[] newArray(int size) {
+ return new CompatibilityOverridesToRemoveByPackageConfig[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
index 642f79ca7afa..e85afefdc39a 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
@@ -26,6 +26,8 @@ import java.util.Set;
/**
* Parcelable containing compat config change IDs for which to remove overrides for a given
* application.
+ *
+ * <p>This class is separate from CompatibilityOverrideConfig since we only need change IDs.
* @hide
*/
public final class CompatibilityOverridesToRemoveConfig implements Parcelable {
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 418d16e07a75..8847a490e39c 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -22,6 +22,8 @@ import java.util.Map;
parcelable CompatibilityChangeConfig;
parcelable CompatibilityOverrideConfig;
+parcelable CompatibilityOverridesByPackageConfig;
+parcelable CompatibilityOverridesToRemoveByPackageConfig;
parcelable CompatibilityOverridesToRemoveConfig;
parcelable CompatibilityChangeInfo;
/**
@@ -152,6 +154,26 @@ interface IPlatformCompat {
void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
/**
+ * Adds overrides to compatibility changes on release builds for multiple apps.
+ *
+ * <p>The caller to this API needs to hold
+ * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids
+ * in {@code overridesByPackage} need to annotated with {@link
+ * android.compat.annotation.Overridable}.
+ *
+ * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to
+ * be {@code false}.
+ *
+ * <p>Note that this does not kill the app, and therefore overrides read from the app process
+ * will not be updated. Overrides read from the system process do take effect.
+ *
+ * @param overridesByPackage parcelable containing the compat change overrides to be applied
+ * on specific apps by their package name
+ * @throws SecurityException if overriding changes is not permitted
+ */
+ void putAllOverridesOnReleaseBuilds(in CompatibilityOverridesByPackageConfig overridesByPackage);
+
+ /**
* Adds overrides to compatibility changes on release builds.
*
* <p>The caller to this API needs to hold
@@ -206,6 +228,26 @@ interface IPlatformCompat {
boolean clearOverrideForTest(long changeId, String packageName);
/**
+ * Restores the default behaviour for compatibility changes on release builds for multiple apps.
+ *
+ * <p>The caller to this API needs to hold
+ * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids
+ * in {@code overridesToRemoveByPackage} need to annotated with {@link
+ * android.compat.annotation.Overridable}.
+ *
+ * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to
+ * be {@code false}.
+ *
+ * <p>Note that this does not kill the app, and therefore overrides read from the app process
+ * will not be updated. Overrides read from the system process do take effect.
+ *
+ * @param overridesToRemoveByPackage parcelable containing the compat change overrides to be
+ * removed for specific apps by their package name
+ * @throws SecurityException if overriding changes is not permitted
+ */
+ void removeAllOverridesOnReleaseBuilds(in CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage);
+
+ /**
* Restores the default behaviour for compatibility changes on release builds.
*
* <p>The caller to this API needs to hold
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6a6f60e967c1..f90461048a3b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -157,6 +157,12 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled";
+ /**
+ * Whether to show old location indicator on all location accesses.
+ */
+ public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED =
+ "location_indicators_small_enabled";
+
// Flags related to Assistant
/**
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index ea38db304e6d..0d4ad382222f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -42,6 +42,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
@@ -177,6 +179,8 @@ public class InteractionJankMonitor {
public static final int CUJ_USER_SWITCH = 37;
public static final int CUJ_SPLASHSCREEN_AVD = 38;
public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
+ public static final int CUJ_SCREEN_OFF = 40;
+ public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
private static final int NO_STATSD_LOGGING = -1;
@@ -225,6 +229,8 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD,
};
private static volatile InteractionJankMonitor sInstance;
@@ -285,6 +291,8 @@ public class InteractionJankMonitor {
CUJ_USER_SWITCH,
CUJ_SPLASHSCREEN_AVD,
CUJ_SPLASHSCREEN_EXIT_ANIM,
+ CUJ_SCREEN_OFF,
+ CUJ_SCREEN_OFF_SHOW_AOD,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -423,6 +431,16 @@ public class InteractionJankMonitor {
}
/**
+ * @param cujType cuj type
+ * @return true if the cuj is under instrumenting, false otherwise.
+ */
+ public boolean isInstrumenting(@CujType int cujType) {
+ synchronized (mLock) {
+ return mRunningTrackers.contains(cujType);
+ }
+ }
+
+ /**
* Begins a trace session.
*
* @param v an attached view.
@@ -690,6 +708,10 @@ public class InteractionJankMonitor {
return "SPLASHSCREEN_AVD";
case CUJ_SPLASHSCREEN_EXIT_ANIM:
return "SPLASHSCREEN_EXIT_ANIM";
+ case CUJ_SCREEN_OFF:
+ return "SCREEN_OFF";
+ case CUJ_SCREEN_OFF_SHOW_AOD:
+ return "SCREEN_OFF_SHOW_AOD";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index d3224b13e312..faea7706ee14 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -260,25 +260,39 @@ public class TransitionAnimation {
return null;
}
- /** Load animation by attribute Id from android package. */
+ /** Load animation by attribute Id from a specific AnimationStyle resource. */
@Nullable
- public Animation loadDefaultAnimationAttr(int animAttr) {
+ public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
+ boolean translucent) {
+ if (animStyleResId == 0) {
+ return null;
+ }
int resId = Resources.ID_NULL;
Context context = mContext;
if (animAttr >= 0) {
- AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE,
- mDefaultWindowAnimationStyleResId);
+ packageName = packageName != null ? packageName : DEFAULT_PACKAGE;
+ AttributeCache.Entry ent = getCachedAnimations(packageName, animStyleResId);
if (ent != null) {
context = ent.context;
resId = ent.array.getResourceId(animAttr, 0);
}
}
+ if (translucent) {
+ resId = updateToTranslucentAnimIfNeeded(resId);
+ }
if (ResourceId.isValid(resId)) {
return loadAnimationSafely(context, resId, mTag);
}
return null;
}
+ /** Load animation by attribute Id from android package. */
+ @Nullable
+ public Animation loadDefaultAnimationAttr(int animAttr) {
+ return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr,
+ false /* translucent */);
+ }
+
@Nullable
private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
if (mDebug) {
@@ -1024,6 +1038,16 @@ public class TransitionAnimation {
return anim;
}
+ private static int updateToTranslucentAnimIfNeeded(int anim) {
+ if (anim == R.anim.activity_open_enter) {
+ return R.anim.activity_translucent_open_enter;
+ }
+ if (anim == R.anim.activity_close_exit) {
+ return R.anim.activity_translucent_close_exit;
+ }
+ return anim;
+ }
+
private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
int wallpaperTransit) {
if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 21402b6602eb..0eb8c6a3ecf3 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -83,6 +83,7 @@ static void init() {
constexpr int INDIC_MIN_PREFIX = 2;
constexpr int INDIC_MIN_SUFFIX = 2;
+ addHyphenator("am", 1, 1); // Amharic
addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
addHyphenator("be", 2, 2); // Belarusian
addHyphenator("bg", 2, 2); // Bulgarian
@@ -100,6 +101,7 @@ static void init() {
addHyphenator("eu", 2, 2); // Basque
addHyphenator("fr", 2, 3); // French
addHyphenator("ga", 2, 3); // Irish
+ addHyphenator("gl", 2, 2); // Galician
addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
addHyphenator("hr", 2, 2); // Croatian
@@ -107,8 +109,10 @@ static void init() {
// texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
// Going with a more conservative value of (2, 2) for now.
addHyphenator("hy", 2, 2); // Armenian
+ addHyphenator("it", 2, 2); // Italian
addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
addHyphenator("la", 2, 2); // Latin
+ addHyphenator("lt", 2, 2); // Lithuanian
addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
@@ -121,6 +125,7 @@ static void init() {
addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
addHyphenator("tk", 2, 2); // Turkmen
+ addHyphenator("uk", 2, 2); // Ukrainian
addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
// Following two hyphenators do not have pattern files but there is some special logic based on
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index b40657835220..f76b211d7d7b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -716,8 +716,6 @@ message GlobalSettingsProto {
optional SettingProto nr_nsa_tracking_screen_off_mode = 153 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto nsd_on = 83 [ (android.privacy).dest = DEST_AUTOMATIC ];
-
message Ntp {
option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2a2ac64e780..d0fee11c5f26 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1489,8 +1489,26 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_bodySensors"
android:description="@string/permdesc_bodySensors"
+ android:backgroundPermission="android.permission.BODY_SENSORS_BACKGROUND"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to access data from sensors that the user uses to measure what is
+ happening inside their body, such as heart rate. If you're requesting this permission, you
+ must also request {@link #BODY_SENSORS}. Requesting this permission by itself doesn't give
+ you Body sensors access.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.BODY_SENSORS_BACKGROUND"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_bodySensors_background"
+ android:description="@string/permdesc_bodySensors_background"
+ android:protectionLevel="dangerous"
+ android:permissionFlags="hardRestricted" />
+
<!-- Allows an app to use fingerprint hardware.
<p>Protection level: normal
@deprecated Applications should request {@link
@@ -6071,6 +6089,13 @@
<permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
android:protectionLevel="signature|role" />
+ <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8aa92018aa7c..bfc1c83e8fc5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8778,6 +8778,14 @@
<attr name="hotwordDetectionService" format="string" />
</declare-styleable>
+ <!-- Use <code>game-service</code> as the root tag of the XML resource that
+ describes a GameService.
+ Described here are the attributes that can be included in that tag. -->
+ <declare-styleable name="GameService">
+ <!-- The service that hosts active game sessions. This is required. -->
+ <attr name="gameSessionService" format="string" />
+ </declare-styleable>
+
<!-- Use <code>voice-enrollment-application</code>
as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
by the enrollment application.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index be32c42eb30d..bd0604e03fee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5550,4 +5550,7 @@
That button will fire an intent targeted for this package with the mentioned action.
When this resource is empty, that button will not be shown. -->
<string name="config_supervisedUserCreationPackage" translatable="false"></string>
+
+ <!-- Determines whether SafetyCenter feature is enabled. -->
+ <bool name="config_enableSafetyCenter">true</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3d3c86006871..bc127d9c1d06 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3250,6 +3250,8 @@
<public name="supportedTypes" />
<public name="resetEnabledSettingsOnAppDataCleared" />
<public name="supportsStylusHandwriting" />
+ <!-- @hide @SystemApi -->
+ <public name="gameSessionService" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b16e462192f5..1f5f18914daa 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1237,8 +1237,11 @@
<string name="permlab_bodySensors">access body sensors (like heart rate monitors)
</string>
<!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
- <string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors
- that monitor your physical condition, such as your heart rate.</string>
+ <string name="permdesc_bodySensors" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc.</string>
+ <!-- Title of the background body sensors permission, listed so the user can decide whether to allow the application to access body sensor data in the background. [CHAR LIMIT=80] -->
+ <string name="permlab_bodySensors_background">access body sensors (like heart rate monitors) while in the background</string>
+ <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_bodySensors_background" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc. while in the background.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCalendar">Read calendar events and details</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 527865f77257..7687b93cf760 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4634,4 +4634,6 @@
<java-symbol type="string" name="config_systemGameService" />
<java-symbol type="string" name="config_supervisedUserCreationPackage"/>
+
+ <java-symbol type="bool" name="config_enableSafetyCenter" />
</resources>
diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
index d936cad15689..121caef87f6f 100644
--- a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
+++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
@@ -16,6 +16,10 @@
package android.net
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_ETHERNET
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_WIFI
import android.text.format.Time.TIMEZONE_UTC
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
@@ -24,6 +28,8 @@ import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.time.ZoneId
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
private const val TEST_IMSI1 = "TESTIMSI1"
private const val TEST_SSID1 = "TESTISSID1"
@@ -53,4 +59,26 @@ class NetworkPolicyTest {
val restored = NetworkPolicy.getNetworkPolicyFromBackup(stream)
assertEquals(policy, restored)
}
+
+ @Test
+ fun testIsTemplatePersistable() {
+ listOf(MATCH_MOBILE, MATCH_WIFI).forEach {
+ // Verify wildcard templates cannot be persistable.
+ assertFalse(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build()))
+
+ // Verify mobile/wifi templates can be persistable if the Subscriber Id is supplied.
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it)
+ .setSubscriberIds(setOf(TEST_IMSI1)).build()))
+ }
+
+ // Verify bluetooth and ethernet templates can be persistable without any other
+ // field is supplied.
+ listOf(MATCH_BLUETOOTH, MATCH_ETHERNET).forEach {
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build()))
+ }
+
+ // Verify wifi template can be persistable if the Wifi Network Key is supplied.
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(MATCH_WIFI)
+ .setWifiNetworkKey(TEST_SSID1).build()))
+ }
} \ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index c69cb4b7e302..8ed3fac85cb2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -45,6 +45,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -80,12 +81,12 @@ import android.net.Uri;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
+import android.view.View;
import androidx.annotation.CallSuper;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.internal.R;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -93,6 +94,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -256,7 +258,7 @@ public class ChooserActivityTest {
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
assertThat(activity.getAdapter().getServiceTargetCount(), is(0));
- onView(withId(R.id.title)).check(matches(withText("chooser test")));
+ onView(withIdFromRuntimeResource("title")).check(matches(withText("chooser test")));
}
@Test
@@ -275,7 +277,8 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
waitForIdle();
- onView(withId(R.id.title)).check(matches(withText(R.string.whichSendApplication)));
+ onView(withIdFromRuntimeResource("title"))
+ .check(matches(withTextFromRuntimeResource("whichSendApplication")));
}
@Test
@@ -294,8 +297,8 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.title))
- .check(matches(withText(R.string.whichSendApplication)));
+ onView(withIdFromRuntimeResource("title"))
+ .check(matches(withTextFromRuntimeResource("whichSendApplication")));
}
@Test
@@ -314,8 +317,10 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_title)).check(matches(not(isDisplayed())));
- onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_title"))
+ .check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ .check(matches(not(isDisplayed())));
}
@Test
@@ -335,9 +340,12 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_title)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_title)).check(matches(withText(previewTitle)));
- onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_title"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_title"))
+ .check(matches(withText(previewTitle)));
+ onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ .check(matches(not(isDisplayed())));
}
@Test
@@ -358,8 +366,9 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_title)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ .check(matches(not(isDisplayed())));
}
@Test
@@ -382,8 +391,9 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_title)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ .check(matches(isDisplayed()));
}
@Test @Ignore
@@ -406,7 +416,7 @@ public class ChooserActivityTest {
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
- onView(withId(R.id.profile_button)).check(doesNotExist());
+ onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
@@ -536,8 +546,8 @@ public class ChooserActivityTest {
waitForIdle();
assertThat(activity.isFinishing(), is(false));
- onView(withId(R.id.empty)).check(matches(isDisplayed()));
- onView(withId(R.id.profile_pager)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("empty")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("profile_pager")).check(matches(not(isDisplayed())));
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> wrapper.getAdapter().handlePackagesChanged()
);
@@ -700,8 +710,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
- onView(withId(R.id.chooser_copy_button)).perform(click());
+ onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
@@ -729,8 +739,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
- onView(withId(R.id.chooser_copy_button)).perform(click());
+ onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
@@ -755,8 +765,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.chooser_nearby_button)).check(matches(isDisplayed()));
- onView(withId(R.id.chooser_nearby_button)).perform(click());
+ onView(withIdFromRuntimeResource("chooser_nearby_button")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("chooser_nearby_button")).perform(click());
ChooserActivityLoggerFake logger =
(ChooserActivityLoggerFake) activity.getChooserActivityLogger();
@@ -824,8 +834,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.chooser_edit_button)).check(matches(isDisplayed()));
- onView(withId(R.id.chooser_edit_button)).perform(click());
+ onView(withIdFromRuntimeResource("chooser_edit_button")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("chooser_edit_button")).perform(click());
ChooserActivityLoggerFake logger =
(ChooserActivityLoggerFake) activity.getChooserActivityLogger();
@@ -897,10 +907,14 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_image_2_large)).check(matches(not(isDisplayed())));
- onView(withId(R.id.content_preview_image_2_small)).check(matches(not(isDisplayed())));
- onView(withId(R.id.content_preview_image_3_small)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ .check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ .check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ .check(matches(not(isDisplayed())));
}
@Test
@@ -929,10 +943,14 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_image_2_large)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_image_2_small)).check(matches(not(isDisplayed())));
- onView(withId(R.id.content_preview_image_3_small)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ .check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ .check(matches(not(isDisplayed())));
}
@Test
@@ -964,10 +982,14 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_image_2_large)).check(matches(not(isDisplayed())));
- onView(withId(R.id.content_preview_image_2_small)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_image_3_small)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ .check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ .check(matches(isDisplayed()));
}
@Test
@@ -1120,9 +1142,11 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename"))
+ .check(matches(withText("app.pdf")));
+ onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ .check(matches(isDisplayed()));
}
@@ -1150,9 +1174,12 @@ public class ChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf + 2 files")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename"))
+ .check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename"))
+ .check(matches(withText("app.pdf + 2 files")));
+ onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ .check(matches(isDisplayed()));
}
@Test
@@ -1179,9 +1206,11 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename"))
+ .check(matches(withText("app.pdf")));
+ onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ .check(matches(isDisplayed()));
}
@Test
@@ -1215,9 +1244,11 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf + 1 file")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("content_preview_filename"))
+ .check(matches(withText("app.pdf + 1 file")));
+ onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ .check(matches(isDisplayed()));
}
@Test
@@ -1296,7 +1327,10 @@ public class ChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.resources
- .getString(R.string.config_defaultAppPredictionService))
+ .getString(
+ getRuntimeResourceId(
+ "config_defaultAppPredictionService",
+ "string")))
.thenReturn("ComponentNameThatDoesNotExist");
assertThat(activity.isAppPredictionServiceAvailable(), is(false));
@@ -1544,7 +1578,8 @@ public class ChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.resources
- .getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .getInteger(
+ getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer")))
.thenReturn(1);
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
@@ -1615,7 +1650,8 @@ public class ChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.resources
- .getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .getInteger(
+ getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer")))
.thenReturn(1);
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
@@ -1787,7 +1823,7 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("tabs")).check(matches(isDisplayed()));
}
@Test
@@ -1800,7 +1836,7 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ onView(withIdFromRuntimeResource("tabs")).check(matches(not(isDisplayed())));
}
@Test
@@ -1825,7 +1861,7 @@ public class ChooserActivityTest {
waitForIdle();
assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
@@ -1848,7 +1884,7 @@ public class ChooserActivityTest {
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
@@ -1875,7 +1911,7 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
// wait for the share sheet to expand
Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
@@ -1906,12 +1942,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_cross_profile_blocked))
+ onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
.check(matches(isDisplayed()));
}
@@ -1932,12 +1968,12 @@ public class ChooserActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_turn_on_work_apps))
+ onView(withTextFromRuntimeResource("resolver_turn_on_work_apps"))
.check(matches(isDisplayed()));
}
@@ -1956,12 +1992,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available))
+ onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
.check(matches(isDisplayed()));
}
@@ -1982,12 +2018,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cross_profile_blocked))
+ onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
.check(matches(isDisplayed()));
}
@@ -2007,12 +2043,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available))
+ onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
.check(matches(isDisplayed()));
}
@@ -2036,7 +2072,7 @@ public class ChooserActivityTest {
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
- onView(withId(R.id.profile_button)).check(doesNotExist());
+ onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
@@ -2265,8 +2301,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
- onView(withId(R.id.chooser_copy_button)).perform(click());
+ onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
+ onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
ChooserActivityLoggerFake logger =
(ChooserActivityLoggerFake) activity.getChooserActivityLogger();
@@ -2329,9 +2365,9 @@ public class ChooserActivityTest {
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_personal_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_personal_tab")).perform(click());
waitForIdle();
ChooserActivityLoggerFake logger =
@@ -2576,12 +2612,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(chooserIntent);
waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_cross_profile_blocked))
+ onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
.check(matches(isDisplayed()));
}
@@ -2610,12 +2646,12 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(chooserIntent);
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available))
+ onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
.check(matches(isDisplayed()));
}
@@ -2675,9 +2711,9 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertFalse("Direct share targets were queried on a paused work profile",
@@ -2707,9 +2743,9 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertFalse("Direct share targets were queried on a locked work profile user",
@@ -2734,9 +2770,8 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
final IChooserWrapper wrapper = (IChooserWrapper) activity;
waitForIdle();
- onView(withId(R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withIdFromRuntimeResource("contentPanel")).perform(swipeUp());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertEquals(3, wrapper.getWorkListAdapter().getCount());
@@ -2765,9 +2800,9 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertFalse("Direct share targets were queried on a locked work profile user",
@@ -2792,9 +2827,9 @@ public class ChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
final IChooserWrapper wrapper = (IChooserWrapper) activity;
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withIdFromRuntimeResource("contentPanel"))
.perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
+ onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
assertEquals(3, wrapper.getWorkListAdapter().getCount());
@@ -3043,4 +3078,27 @@ public class ChooserActivityTest {
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
}
+
+ private Matcher<View> withIdFromRuntimeResource(String id) {
+ return withId(getRuntimeResourceId(id, "id"));
+ }
+
+ private Matcher<View> withTextFromRuntimeResource(String id) {
+ return withText(getRuntimeResourceId(id, "string"));
+ }
+
+ // ChooserWrapperActivity inherits from the framework ChooserActivity, so if the framework
+ // resources have been updated since the framework was last built/pushed, the inherited behavior
+ // (which is the focus of our testing) will still be implemented in terms of the old resource
+ // IDs; then when we try to assert those IDs in tests (e.g. `onView(withText(R.string.foo))`),
+ // the expected values won't match. The tests can instead call this method (with the same
+ // general semantics as Resources#getIdentifier() e.g. `getRuntimeResourceId("foo", "string")`)
+ // to refer to the resource by that name in the runtime chooser, regardless of whether the
+ // framework code on the device is up-to-date.
+ // TODO: is there a better way to do this? (Other than abandoning inheritance-based DI wrapper?)
+ private int getRuntimeResourceId(String name, String defType) {
+ int id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
+ assertThat(id, greaterThan(0));
+ return id;
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0b8dc3fe0dec..92fca3661fbc 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,6 +223,10 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</split-permission>
+ <split-permission name="android.permission.BODY_SENSORS"
+ targetSdk="33">
+ <new-permission name="android.permission.BODY_SENSORS_BACKGROUND" />
+ </split-permission>
<split-permission name="android.permission.READ_EXTERNAL_STORAGE"
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e61a665307f8..f17fa3b17fe5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -520,6 +520,8 @@ applications that come with the platform
<permission name="android.permission.LOCK_DEVICE" />
<!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<!-- Permission required for CTS test - CommunalManagerTest -->
<permission name="android.permission.WRITE_COMMUNAL_STATE" />
<permission name="android.permission.READ_COMMUNAL_STATE" />
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index b77865f423c6..fc7f84c3c83e 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -118,13 +118,13 @@ public final class Outline {
/**
* Returns whether the outline can be used to clip a View.
* <p>
- * Currently, only Outlines that can be represented as a rectangle, circle,
- * or round rect support clipping.
+ * As of API 33, all Outline shapes support clipping. Prior to API 33, only Outlines that
+ * could be represented as a rectangle, circle, or round rect supported clipping.
*
* @see android.view.View#setClipToOutline(boolean)
*/
public boolean canClip() {
- return mMode != MODE_PATH;
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index a006f308d694..3de59b48bcf0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -25,6 +25,7 @@ import android.app.ActivityTaskManager;
import android.app.TaskInfo;
import android.content.Context;
import android.os.RemoteException;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -316,6 +317,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
(controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
.toArray(new GroupedRecentTaskInfo[0]),
true /* blocking */);
+ Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]);
return out[0];
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index a91dfe1c13e2..448773ae9ea2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -76,12 +76,6 @@ public interface SplitScreen {
}
/**
- * Called when the keyguard occluded state changes.
- * @param occluded Indicates if the keyguard is now occluded.
- */
- void onKeyguardOccludedChanged(boolean occluded);
-
- /**
* Called when the visibility of the keyguard changes.
* @param showing Indicates if the keyguard is now visible.
*/
@@ -90,9 +84,6 @@ public interface SplitScreen {
/** Called when device waking up finished. */
void onFinishedWakingUp();
- /** Called when device going to sleep finished. */
- void onFinishedGoingToSleep();
-
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 18cabd3ddb9a..1f49a4cc3ab9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -249,10 +249,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
}
- public void onKeyguardOccludedChanged(boolean occluded) {
- mStageCoordinator.onKeyguardOccludedChanged(occluded);
- }
-
public void onKeyguardVisibilityChanged(boolean showing) {
mStageCoordinator.onKeyguardVisibilityChanged(showing);
}
@@ -261,10 +257,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onFinishedWakingUp();
}
- public void onFinishedGoingToSleep() {
- mStageCoordinator.onFinishedGoingToSleep();
- }
-
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
}
@@ -491,13 +483,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void onKeyguardOccludedChanged(boolean occluded) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardOccludedChanged(occluded);
- });
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -538,13 +523,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
SplitScreenController.this.onFinishedWakingUp();
});
}
-
- @Override
- public void onFinishedGoingToSleep() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedGoingToSleep();
- });
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d2176866283b..2385ec90ab23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -46,7 +46,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -157,8 +156,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mKeyguardOccluded;
- private boolean mDeviceSleep;
/** The target stage to dismiss to when unlock after folded. */
@StageType
@@ -171,6 +168,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// properly for the animation itself.
mSplitLayout.release();
mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
};
@@ -554,29 +552,54 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
- void onKeyguardOccludedChanged(boolean occluded) {
- // Do not exit split directly, because it needs to wait for task info update to determine
- // which task should remain on top after split dismissed.
- mKeyguardOccluded = occluded;
- }
-
void onKeyguardVisibilityChanged(boolean showing) {
- if (!showing && mMainStage.isActive()
- && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
+ if (!mMainStage.isActive()) {
+ return;
+ }
+
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Update divider visibility so it won't float on top of keyguard.
+ setDividerVisibility(!showing, null /* transaction */);
+ }
+
+ if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
+ }
}
}
void onFinishedWakingUp() {
- if (mMainStage.isActive()) {
- exitSplitScreenIfKeyguardOccluded();
+ if (!mMainStage.isActive()) {
+ return;
}
- mDeviceSleep = false;
- }
- void onFinishedGoingToSleep() {
- mDeviceSleep = true;
+ // Check if there's only one stage visible while keyguard occluded.
+ final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
+ final boolean oneStageVisible =
+ mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
+ if (oneStageVisible) {
+ // Dismiss split because there's show-when-locked activity showing on top of keyguard.
+ // Also make sure the task contains show-when-locked activity remains on top after split
+ // dismissed.
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ } else {
+ final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(dismissTop, wct);
+ mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+ }
}
void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -607,19 +630,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
- private void exitSplitScreenIfKeyguardOccluded() {
- final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean oneStageVisible = mainStageVisible ^ mSideStageListener.mVisible;
- if (mDeviceSleep && mKeyguardOccluded && oneStageVisible) {
- // Only the stages include show-when-locked activity is visible while keyguard occluded.
- // Dismiss split because there's show-when-locked activity showing on top of keyguard.
- // Also make sure the task contains show-when-locked activity remains on top after split
- // dismissed.
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
- }
-
private void applyExitSplitScreen(StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
mRecentTasks.ifPresent(recentTasks -> {
@@ -669,6 +679,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case EXIT_REASON_APP_FINISHED:
// One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
+ // One of the apps occludes lock screen.
+ case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
+ // User has unlocked the device after folded
+ case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
return false;
@@ -828,31 +842,27 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
final boolean sideStageVisible = mSideStageListener.mVisible;
final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean bothStageVisible = sideStageVisible && mainStageVisible;
- final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
- final boolean sameVisibility = sideStageVisible == mainStageVisible;
- if (bothStageInvisible) {
+ // Wait for both stages having the same visibility to prevent causing flicker.
+ if (mainStageVisible != sideStageVisible) {
+ return;
+ }
+
+ if (!mainStageVisible) {
+ // Both stages are not visible, check if it needs to dismiss split screen.
if (mExitSplitScreenOnHide
- // Don't dismiss staged split when both stages are not visible due to sleeping
- // display, like the cases keyguard showing or screen off.
+ // Don't dismiss split screen when both stages are not visible due to sleeping
+ // display.
|| (!mMainStage.mRootTaskInfo.isSleeping
&& !mSideStage.mRootTaskInfo.isSleeping)) {
- // Don't dismiss staged split when both stages are not visible due to sleeping display,
- // like the cases keyguard showing or screen off.
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
}
}
- exitSplitScreenIfKeyguardOccluded();
mSyncQueue.runInSync(t -> {
- // Same above, we only set root tasks and divider leash visibility when both stage
- // change to visible or invisible to avoid flicker.
- if (sameVisibility) {
- t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
- .setVisibility(mMainStage.mRootLeash, bothStageVisible);
- setDividerVisibility(bothStageVisible, t);
- }
+ t.setVisibility(mSideStage.mRootLeash, sideStageVisible)
+ .setVisibility(mMainStage.mRootLeash, mainStageVisible);
+ setDividerVisibility(mainStageVisible, t);
});
}
@@ -1374,11 +1384,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSplitsVisible(false);
// Wait until after animation to update divider
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
- }
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
logExit(dismissTransition.mReason);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 324b8b53e433..0c5ec6e465cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
@@ -509,60 +510,70 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
// This received a transferred starting window, so don't animate
return null;
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
- : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
- } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
- : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
- } else if (type == TRANSIT_OPEN) {
- if (isTask) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskOpenEnterAnimation
- : R.styleable.WindowAnimation_taskOpenExitAnimation);
- } else {
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- R.anim.activity_translucent_open_enter);
+ } else {
+ int animAttr = 0;
+ boolean translucent = false;
+ if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (type == TRANSIT_OPEN) {
+ if (isTask) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+ : R.styleable.WindowAnimation_taskOpenExitAnimation;
} else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+ translucent = true;
+ }
+ animAttr = enter
? R.styleable.WindowAnimation_activityOpenEnterAnimation
- : R.styleable.WindowAnimation_activityOpenExitAnimation);
+ : R.styleable.WindowAnimation_activityOpenExitAnimation;
}
- }
- } else if (type == TRANSIT_TO_FRONT) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
- : R.styleable.WindowAnimation_taskToFrontExitAnimation);
- } else if (type == TRANSIT_CLOSE) {
- if (isTask) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskCloseEnterAnimation
- : R.styleable.WindowAnimation_taskCloseExitAnimation);
- } else {
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- R.anim.activity_translucent_close_exit);
+ } else if (type == TRANSIT_TO_FRONT) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+ : R.styleable.WindowAnimation_taskToFrontExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ if (isTask) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+ : R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+ translucent = true;
+ }
+ animAttr = enter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
- : R.styleable.WindowAnimation_activityCloseExitAnimation);
+ : R.styleable.WindowAnimation_activityCloseExitAnimation;
+ }
+ } else if (type == TRANSIT_TO_BACK) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+ : R.styleable.WindowAnimation_taskToBackExitAnimation;
+ }
+
+ if (animAttr != 0) {
+ if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+ a = mTransitionAnimation
+ .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+ animAttr, translucent);
+ } else {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr);
}
}
- } else if (type == TRANSIT_TO_BACK) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(enter
- ? R.styleable.WindowAnimation_taskToBackEnterAnimation
- : R.styleable.WindowAnimation_taskToBackExitAnimation);
}
if (a != null) {
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 2eb2c7c7e299..e16fd8c38c75 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -88,14 +88,10 @@ public:
bool getShouldClip() const { return mShouldClip; }
- bool willClip() const {
- // only round rect outlines can be used for clipping
- return mShouldClip && (mType == Type::RoundRect);
- }
+ bool willClip() const { return mShouldClip; }
- bool willRoundRectClip() const {
- // only round rect outlines can be used for clipping
- return willClip() && MathUtils::isPositive(mRadius);
+ bool willComplexClip() const {
+ return mShouldClip && (mType != Type::RoundRect || MathUtils::isPositive(mRadius));
}
bool getAsRoundRect(Rect* outRect, float* outRadius) const {
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index dd8439647fd3..3d0ca0a10851 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -137,6 +137,7 @@ void ProfileData::dump(int fd) const {
histogramGPUForEach([fd](HistogramEntry entry) {
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
});
+ dprintf(fd, "\n");
}
uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index cd622eba37b6..064ba7aee107 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -165,11 +165,11 @@ public:
bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) {
// parent may have already dictated that a descendant layer is needed
bool functorsNeedLayer =
- ancestorDictatesFunctorsNeedLayer
- || CC_UNLIKELY(isClipMayBeComplex())
+ ancestorDictatesFunctorsNeedLayer ||
+ CC_UNLIKELY(isClipMayBeComplex())
// Round rect clipping forces layer for functors
- || CC_UNLIKELY(getOutline().willRoundRectClip()) ||
+ || CC_UNLIKELY(getOutline().willComplexClip()) ||
CC_UNLIKELY(getRevealClip().willClip())
// Complex matrices forces layer, due to stencil clipping
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 48145d2331ee..507d3dcdcde9 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -88,6 +88,10 @@ static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect*
if (pendingClip) {
canvas->clipRect(*pendingClip);
}
+ const SkPath* path = outline.getPath();
+ if (path) {
+ canvas->clipPath(*path, SkClipOp::kIntersect, true);
+ }
return;
}
diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
new file mode 100644
index 000000000000..1e343c1dd283
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "TestSceneBase.h"
+
+class PathClippingAnimation : public TestScene {
+public:
+ int mSpacing, mSize;
+ bool mClip, mAnimateClip;
+ int mMaxCards;
+ std::vector<sp<RenderNode> > cards;
+
+ PathClippingAnimation(int spacing, int size, bool clip, bool animateClip, int maxCards)
+ : mSpacing(spacing)
+ , mSize(size)
+ , mClip(clip)
+ , mAnimateClip(animateClip)
+ , mMaxCards(maxCards) {}
+
+ PathClippingAnimation(int spacing, int size, bool clip, bool animateClip)
+ : PathClippingAnimation(spacing, size, clip, animateClip, INT_MAX) {}
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
+ canvas.enableZ(true);
+ int ci = 0;
+ int numCards = 0;
+
+ for (int x = 0; x < width; x += mSpacing) {
+ for (int y = 0; y < height; y += mSpacing) {
+ auto color = BrightColors[ci++ % BrightColorsCount];
+ auto card = TestUtils::createNode(
+ x, y, x + mSize, y + mSize, [&](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(color, SkBlendMode::kSrcOver);
+ if (mClip) {
+ // Create circular path that rounds around the inside of all
+ // four corners of the given square defined by mSize*mSize
+ SkPath path = setPath(mSize);
+ props.mutableOutline().setPath(&path, 1);
+ props.mutableOutline().setShouldClip(true);
+ }
+ });
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ ++numCards;
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+
+ canvas.enableZ(false);
+ }
+
+ SkPath setPath(int size) {
+ SkPath path;
+ path.moveTo(0, size / 2);
+ path.cubicTo(0, size * .75, size * .25, size, size / 2, size);
+ path.cubicTo(size * .75, size, size, size * .75, size, size / 2);
+ path.cubicTo(size, size * .25, size * .75, 0, size / 2, 0);
+ path.cubicTo(size / 4, 0, 0, size / 4, 0, size / 2);
+ return path;
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 50;
+ if (curFrame > 25) curFrame = 50 - curFrame;
+ for (auto& card : cards) {
+ if (mAnimateClip) {
+ SkPath path = setPath(mSize - curFrame);
+ card->mutateStagingProperties().mutableOutline().setPath(&path, 1);
+ }
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::DISPLAY_LIST);
+ }
+ }
+};
+
+static TestScene::Registrar _PathClippingUnclipped(TestScene::Info{
+ "pathClipping-unclipped", "Multiple RenderNodes, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), false, false);
+ }});
+
+static TestScene::Registrar _PathClippingUnclippedSingle(TestScene::Info{
+ "pathClipping-unclippedsingle", "A single RenderNode, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), false, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingUnclippedSingleLarge(TestScene::Info{
+ "pathClipping-unclippedsinglelarge", "A single large RenderNode, unclipped.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), false, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingClipped80(TestScene::Info{
+ "pathClipping-clipped80", "Multiple RenderNodes, clipped by paths.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, false);
+ }});
+
+static TestScene::Registrar _PathClippingClippedSingle(TestScene::Info{
+ "pathClipping-clippedsingle", "A single RenderNode, clipped by a path.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingClippedSingleLarge(TestScene::Info{
+ "pathClipping-clippedsinglelarge", "A single large RenderNode, clipped by a path.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), true, false, 1);
+ }});
+
+static TestScene::Registrar _PathClippingAnimated(TestScene::Info{
+ "pathClipping-animated",
+ "Multiple RenderNodes, clipped by paths which are being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, true);
+ }});
+
+static TestScene::Registrar _PathClippingAnimatedSingle(TestScene::Info{
+ "pathClipping-animatedsingle",
+ "A single RenderNode, clipped by a path which is being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(80), true, true, 1);
+ }});
+
+static TestScene::Registrar _PathClippingAnimatedSingleLarge(TestScene::Info{
+ "pathClipping-animatedsinglelarge",
+ "A single large RenderNode, clipped by a path which is being altered every frame.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new PathClippingAnimation(dp(100), dp(350), true, true, 1);
+ }});
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index 163745b04ed2..e9f353d887f2 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -21,14 +21,17 @@
class RoundRectClippingAnimation : public TestScene {
public:
int mSpacing, mSize;
+ int mMaxCards;
- RoundRectClippingAnimation(int spacing, int size) : mSpacing(spacing), mSize(size) {}
+ RoundRectClippingAnimation(int spacing, int size, int maxCards = INT_MAX)
+ : mSpacing(spacing), mSize(size), mMaxCards(maxCards) {}
std::vector<sp<RenderNode> > cards;
void createContent(int width, int height, Canvas& canvas) override {
canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.enableZ(true);
int ci = 0;
+ int numCards = 0;
for (int x = 0; x < width; x += mSpacing) {
for (int y = 0; y < height; y += mSpacing) {
@@ -42,6 +45,13 @@ public:
});
canvas.drawRenderNode(card.get());
cards.push_back(card);
+ ++numCards;
+ if (numCards >= mMaxCards) {
+ break;
+ }
+ }
+ if (numCards >= mMaxCards) {
+ break;
}
}
@@ -71,3 +81,22 @@ static TestScene::Registrar _RoundRectClippingCpu(TestScene::Info{
[](const TestScene::Options&) -> test::TestScene* {
return new RoundRectClippingAnimation(dp(20), dp(20));
}});
+
+static TestScene::Registrar _RoundRectClippingGrid(TestScene::Info{
+ "roundRectClipping-grid", "A grid of RenderNodes with round rect clipping outlines.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(80));
+ }});
+
+static TestScene::Registrar _RoundRectClippingSingle(TestScene::Info{
+ "roundRectClipping-single", "A single RenderNodes with round rect clipping outline.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(80), 1);
+ }});
+
+static TestScene::Registrar _RoundRectClippingSingleLarge(TestScene::Info{
+ "roundRectClipping-singlelarge",
+ "A single large RenderNodes with round rect clipping outline.",
+ [](const TestScene::Options&) -> test::TestScene* {
+ return new RoundRectClippingAnimation(dp(100), dp(350), 1);
+ }});
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 0722417c0d18..dd27cf140ca1 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6788,56 +6788,63 @@ public class AudioManager {
/**
* Returns a list of audio formats that corresponds to encoding formats
- * supported on offload path for A2DP and LE audio playback.
+ * supported on offload path for A2DP playback.
*
- * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType}
* @return a list of {@link BluetoothCodecConfig} objects containing encoding formats
- * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig}
- * objects containing encoding formats supported for offload LE Audio playback
+ * supported for offload A2DP playback
* @hide
*/
- public List<?> getHwOffloadFormatsSupportedForBluetoothMedia(
- @AudioSystem.DeviceType int deviceType) {
- ArrayList<Integer> formatsList = new ArrayList<Integer>();
- ArrayList<BluetoothCodecConfig> a2dpCodecConfigList = new ArrayList<BluetoothCodecConfig>();
- ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList =
- new ArrayList<BluetoothLeAudioCodecConfig>();
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NonNull List<BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp() {
+ ArrayList<Integer> formatsList = new ArrayList<>();
+ ArrayList<BluetoothCodecConfig> codecConfigList = new ArrayList<>();
- if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
- && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) {
- throw new IllegalArgumentException(
- "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia");
+ int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, formatsList);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status);
+ return codecConfigList;
+ }
+
+ for (Integer format : formatsList) {
+ int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
+ if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ codecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+ }
}
+ return codecConfigList;
+ }
+
+ /**
+ * Returns a list of audio formats that corresponds to encoding formats
+ * supported on offload path for Le audio playback.
+ *
+ * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats
+ * supported for offload Le Audio playback
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio() {
+ ArrayList<Integer> formatsList = new ArrayList<>();
+ ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList = new ArrayList<>();
- int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType,
- formatsList);
+ int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(
+ AudioSystem.DEVICE_OUT_BLE_HEADSET, formatsList);
if (status != AudioManager.SUCCESS) {
- Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType "
- + deviceType + " failed:" + status);
- return a2dpCodecConfigList;
+ Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForLeAudio failed:" + status);
+ return leAudioCodecConfigList;
}
- if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- for (Integer format : formatsList) {
- int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
- if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
- }
+ for (Integer format : formatsList) {
+ int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
+ if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
+ .setCodecType(btLeAudioCodec)
+ .build());
}
- return a2dpCodecConfigList;
- } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
- for (Integer format : formatsList) {
- int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
- if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
- .setCodecType(btLeAudioCodec)
- .build());
- }
- }
- return leAudioCodecConfigList;
}
- Log.e(TAG, "Input deviceType " + deviceType + " doesn't support.");
- return a2dpCodecConfigList;
+ return leAudioCodecConfigList;
}
// Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index dcd4dce5f3eb..ec56d614f2b5 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -16,8 +16,11 @@
package android.media;
-import android.media.AudioManager;
+import android.content.Context;
import android.media.SoundPool;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
/**
@@ -104,6 +107,26 @@ public class MediaActionSound {
private static final int STATE_LOADING_PLAY_REQUESTED = 2;
private static final int STATE_LOADED = 3;
+ /**
+ * <p>Returns true if the application must play the shutter sound in accordance
+ * to certain regional restrictions. </p>
+ *
+ * <p>If this method returns true, applications are strongly recommended to use
+ * MediaActionSound.play(SHUTTER_CLICK) or START_VIDEO_RECORDING whenever it captures
+ * images or video to storage or sends them over the network.</p>
+ */
+ public static boolean mustPlayShutterSound() {
+ boolean result = false;
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ try {
+ result = audioService.isCameraSoundForced();
+ } catch (RemoteException e) {
+ Log.e(TAG, "audio service is unavailable for queries, defaulting to false");
+ }
+ return result;
+ }
+
private class SoundState {
public final int name;
public int id;
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 255b391b2259..41f926520a13 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1577,6 +1577,20 @@ public class Tuner implements AutoCloseable {
}
}
+ private void onDvbtCellIdsReported(int[] dvbtCellIds) {
+ synchronized (mScanCallbackLock) {
+ if (mScanCallbackExecutor != null && mScanCallback != null) {
+ mScanCallbackExecutor.execute(() -> {
+ synchronized (mScanCallbackLock) {
+ if (mScanCallback != null) {
+ mScanCallback.onDvbtCellIdsReported(dvbtCellIds);
+ }
+ }
+ });
+ }
+ }
+ }
+
/**
* Opens a filter object based on the given types and buffer size.
*
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 92eafec6a13a..8cedd04a8b89 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -53,7 +53,8 @@ public class FrontendStatus {
FRONTEND_STATUS_TYPE_MODULATIONS_EXT, FRONTEND_STATUS_TYPE_ROLL_OFF,
FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
- FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS})
+ FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS,
+ FRONTEND_STATUS_TYPE_DVBT_CELL_IDS})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendStatusType {}
@@ -260,6 +261,12 @@ public class FrontendStatus {
public static final int FRONTEND_STATUS_TYPE_STREAM_IDS =
android.hardware.tv.tuner.FrontendStatusType.STREAM_ID_LIST;
+ /**
+ * DVB-T Cell IDs.
+ */
+ public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS =
+ android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS;
+
/** @hide */
@IntDef(value = {
AtscFrontendSettings.MODULATION_UNDEFINED,
@@ -500,6 +507,7 @@ public class FrontendStatus {
private Integer mIsdbtMode;
private Integer mIsdbtPartialReceptionFlag;
private int[] mStreamIds;
+ private int[] mDvbtCellIds;
// Constructed and fields set by JNI code.
private FrontendStatus() {
@@ -1052,6 +1060,24 @@ public class FrontendStatus {
}
/**
+ * Gets DVB-T cell ids.
+ *
+ * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
+ * doesn't return cell ids will throw IllegalStateException. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @SuppressLint("ArrayReturn")
+ @NonNull
+ public int[] getDvbtCellIds() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0, "dvbt cell ids status");
+ if (mDvbtCellIds == null) {
+ throw new IllegalStateException("dvbt cell ids are empty");
+ }
+ return mDvbtCellIds;
+ }
+
+ /**
* Information of each tuning Physical Layer Pipes.
*/
public static class Atsc3PlpTuningInfo {
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index cb35edb15c62..8917050fcd24 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -94,4 +94,7 @@ public interface ScanCallback {
/** DVBC Frontend Annex reported. */
default void onDvbcAnnexReported(@DvbcFrontendSettings.Annex int dvbcAnnex) {}
+
+ /** DVBT Frontend Cell Ids reported. */
+ default void onDvbtCellIdsReported(@NonNull int[] dvbtCellIds) {}
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index e91e2381bd42..712add4ffc3c 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1191,6 +1191,14 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
dvbcAnnex);
break;
}
+ case FrontendScanMessageType::DVBT_CELL_IDS: {
+ std::vector<int32_t> jintV = message.get<FrontendScanMessage::dvbtCellIds>();
+ jintArray cellIds = env->NewIntArray(jintV.size());
+ env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
+ env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
+ cellIds);
+ break;
+ }
default:
break;
}
@@ -2519,6 +2527,16 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetObjectField(statusObj, field, valObj);
break;
}
+ case FrontendStatus::Tag::dvbtCellIds: {
+ jfieldID field = env->GetFieldID(clazz, "mDvbtCellIds", "[I");
+ std::vector<int32_t> ids = s.get<FrontendStatus::Tag::dvbtCellIds>();
+
+ jintArray valObj = env->NewIntArray(v.size());
+ env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+
+ env->SetObjectField(statusObj, field, valObj);
+ break;
+ }
}
}
return statusObj;
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index c87bac67fdff..313e164cdbef 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/activity_confirmation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dialog_background"
@@ -23,6 +24,8 @@
android:padding="18dp"
android:layout_gravity="center">
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
+
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -30,7 +33,6 @@
android:gravity="center"
android:paddingHorizontal="12dp"
style="@*android:style/TextAppearance.Widget.Toolbar.Title"/>
- <!-- style="@*android:style/TextAppearance.Widget.Toolbar.Title" -->
<TextView
android:id="@+id/summary"
@@ -61,8 +63,10 @@
android:orientation="horizontal"
android:gravity="end">
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
<Button
- android:id="@+id/button_cancel"
+ android:id="@+id/btn_negative"
style="@android:style/Widget.Material.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -70,7 +74,7 @@
android:textColor="?android:attr/textColorSecondary" />
<Button
- android:id="@+id/button_allow"
+ android:id="@+id/btn_positive"
style="@android:style/Widget.Material.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
new file mode 100644
index 000000000000..d79aea6fae6b
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_item_device"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:padding="12dp">
+
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginRight="12dp"/>
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index cc887c34414e..d84df51b9574 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -21,6 +21,8 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE;
+import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.TIMEOUT_OBSERVABLE;
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
@@ -91,10 +93,13 @@ public class CompanionDeviceActivity extends Activity {
// The flag used to prevent double taps, that may lead to sending several requests for creating
// an association to CDM.
- private boolean mAssociationApproved;
+ private boolean mApproved;
+ private boolean mCancelled;
@Override
public void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreate()");
+
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
}
@@ -117,26 +122,13 @@ public class CompanionDeviceActivity extends Activity {
// Start discovery services if needed.
if (!mRequest.isSelfManaged()) {
CompanionDeviceDiscoveryService.startForRequest(this, mRequest);
+ TIMEOUT_OBSERVABLE.addObserver((o, arg) -> cancel(true));
}
// Init UI.
initUI();
}
@Override
- protected void onStop() {
- super.onStop();
- if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
-
- // TODO: handle config changes without cancelling.
- if (!isFinishing()) {
- cancel(); // will finish()
- }
-
- // mAdapter may be observing - need to remove it.
- CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.deleteObservers();
- }
-
- @Override
protected void onNewIntent(Intent intent) {
// Handle another incoming request (while we are not done with the original - mRequest -
// yet).
@@ -153,6 +145,39 @@ public class CompanionDeviceActivity extends Activity {
}
}
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
+
+ // TODO: handle config changes without cancelling.
+ if (!isDone()) {
+ cancel(false); // will finish()
+ }
+
+ TIMEOUT_OBSERVABLE.deleteObservers();
+ // mAdapter may also be observing - need to remove it.
+ SCAN_RESULTS_OBSERVABLE.deleteObservers();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Log.d(TAG, "onDestroy()");
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (DEBUG) Log.d(TAG, "onBackPressed()");
+ super.onBackPressed();
+ }
+
+ @Override
+ public void finish() {
+ if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump"));
+ super.finish();
+ }
+
private void initUI() {
if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
@@ -164,10 +189,9 @@ public class CompanionDeviceActivity extends Activity {
mListView = findViewById(R.id.device_list);
mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position));
- mButtonAllow = findViewById(R.id.button_allow);
- mButtonAllow.setOnClickListener(this::onAllowButtonClick);
-
- findViewById(R.id.button_cancel).setOnClickListener(v -> cancel());
+ mButtonAllow = findViewById(R.id.btn_positive);
+ mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
+ findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
if (mRequest.isSelfManaged()) {
@@ -179,6 +203,33 @@ public class CompanionDeviceActivity extends Activity {
}
}
+ private void onAssociationApproved(@Nullable MacAddress macAddress) {
+ if (isDone()) {
+ if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ return;
+ }
+ mApproved = true;
+
+ if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+
+ if (!mRequest.isSelfManaged()) {
+ requireNonNull(macAddress);
+ CompanionDeviceDiscoveryService.stop(this);
+ }
+
+ final Bundle data = new Bundle();
+ data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
+ data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
+ if (macAddress != null) {
+ data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
+ }
+
+ data.putParcelable(EXTRA_RESULT_RECEIVER,
+ prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
+
+ mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
+ }
+
private void onAssociationCreated(@NonNull AssociationInfo association) {
if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
@@ -186,17 +237,26 @@ public class CompanionDeviceActivity extends Activity {
setResultAndFinish(association);
}
- private void cancel() {
- if (DEBUG) Log.i(TAG, "cancel()");
+ private void cancel(boolean discoveryTimeout) {
+ if (DEBUG) {
+ Log.i(TAG, "cancel(), discoveryTimeout=" + discoveryTimeout,
+ new Exception("Stack Trace Dump"));
+ }
+
+ if (isDone()) {
+ if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ return;
+ }
+ mCancelled = true;
// Stop discovery service if it was used.
- if (!mRequest.isSelfManaged()) {
+ if (!mRequest.isSelfManaged() || discoveryTimeout) {
CompanionDeviceDiscoveryService.stop(this);
}
// First send callback to the app directly...
try {
- mAppCallback.onFailure("Cancelled.");
+ mAppCallback.onFailure(discoveryTimeout ? "Timeout." : "Cancelled.");
} catch (RemoteException ignore) {
}
@@ -297,7 +357,7 @@ public class CompanionDeviceActivity extends Activity {
mSummary.setText(summary);
mAdapter = new DeviceListAdapter(this);
- CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
+ SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
// TODO: hide the list and show a spinner until a first device matching device is found.
mListView.setAdapter(mAdapter);
@@ -313,8 +373,8 @@ public class CompanionDeviceActivity extends Activity {
onAssociationApproved(macAddress);
}
- private void onAllowButtonClick(View v) {
- if (DEBUG) Log.d(TAG, "onAllowButtonClick()");
+ private void onPositiveButtonClick(View v) {
+ if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()");
// Disable the button, to prevent more clicks.
v.setEnabled(false);
@@ -330,28 +390,17 @@ public class CompanionDeviceActivity extends Activity {
onAssociationApproved(macAddress);
}
- private void onAssociationApproved(@Nullable MacAddress macAddress) {
- if (mAssociationApproved) return;
- mAssociationApproved = true;
+ private void onNegativeButtonClick(View v) {
+ if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()");
- if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
-
- if (!mRequest.isSelfManaged()) {
- requireNonNull(macAddress);
- CompanionDeviceDiscoveryService.stop(this);
- }
-
- final Bundle data = new Bundle();
- data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
- data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
- if (macAddress != null) {
- data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
- }
+ // Disable the button, to prevent more clicks.
+ v.setEnabled(false);
- data.putParcelable(EXTRA_RESULT_RECEIVER,
- prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
+ cancel(false);
+ }
- mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
+ private boolean isDone() {
+ return mApproved || mCancelled;
}
private final ResultReceiver mOnAssociationCreatedReceiver =
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index a4ff1dc00fda..f859130804b9 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -22,6 +22,8 @@ import static com.android.internal.util.CollectionUtils.filter;
import static com.android.internal.util.CollectionUtils.find;
import static com.android.internal.util.CollectionUtils.map;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
import static java.util.Objects.requireNonNull;
import android.annotation.MainThread;
@@ -50,6 +52,7 @@ import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
@@ -64,13 +67,17 @@ public class CompanionDeviceDiscoveryService extends Service {
private static final boolean DEBUG = false;
private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
+ private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
+ private static final long TIMEOUT_DEFAULT = 20_000L; // 20 seconds
+ private static final long TIMEOUT_MIN = 1_000L; // 1 sec
+ private static final long TIMEOUT_MAX = 60_000L; // 1 min
+
private static final String ACTION_START_DISCOVERY =
"com.android.companiondevicemanager.action.START_DISCOVERY";
private static final String ACTION_STOP_DISCOVERY =
"com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
- private static final long SCAN_TIMEOUT = 20_000L; // 20 seconds
// TODO: replace with LiveData-s?
static final Observable TIMEOUT_OBSERVABLE = new MyObservable();
@@ -180,8 +187,7 @@ public class CompanionDeviceDiscoveryService extends Service {
// Start BLE scanning (if needed)
mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll);
- // Schedule a time-out.
- Handler.getMain().postDelayed(mTimeoutRunnable, SCAN_TIMEOUT);
+ scheduleTimeout();
}
@MainThread
@@ -338,6 +344,21 @@ public class CompanionDeviceDiscoveryService extends Service {
});
}
+ private void scheduleTimeout() {
+ long timeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1);
+ if (timeout <= 0) {
+ // 0 or negative values indicate that the sysprop was never set or should be ignored.
+ timeout = TIMEOUT_DEFAULT;
+ } else {
+ timeout = min(timeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX)
+ timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
+ }
+
+ if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout);
+
+ Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
+ }
+
private void timeout() {
if (DEBUG) Log.i(TAG, "timeout()");
stopDiscoveryAndFinish();
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index cf2a2bfa468b..2499cf06f507 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -16,18 +16,14 @@
package com.android.companiondevicemanager;
-import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
@@ -39,51 +35,12 @@ import java.util.Observer;
*/
class DeviceListAdapter extends BaseAdapter implements Observer {
private final Context mContext;
- private final Resources mResources;
-
- private final Drawable mBluetoothIcon;
- private final Drawable mWifiIcon;
-
- private final @ColorInt int mTextColor;
// List if pairs (display name, address)
private List<DeviceFilterPair<?>> mDevices;
DeviceListAdapter(Context context) {
mContext = context;
- mResources = context.getResources();
- mBluetoothIcon = getTintedIcon(mResources, android.R.drawable.stat_sys_data_bluetooth);
- mWifiIcon = getTintedIcon(mResources, com.android.internal.R.drawable.ic_wifi_signal_3);
- mTextColor = getColor(context, android.R.attr.colorForeground);
- }
-
- @Override
- public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
- final TextView view = convertView != null ? (TextView) convertView : newView();
- bind(view, getItem(position));
- return view;
- }
-
- private void bind(TextView textView, DeviceFilterPair<?> item) {
- textView.setText(item.getDisplayName());
- textView.setBackgroundColor(Color.TRANSPARENT);
- /*
- textView.setCompoundDrawablesWithIntrinsicBounds(
- item.getDevice() instanceof android.net.wifi.ScanResult
- ? mWifiIcon
- : mBluetoothIcon,
- null, null, null);
- textView.getCompoundDrawables()[0].setTint(mTextColor);
- */
- }
-
- private TextView newView() {
- final TextView textView = new TextView(mContext);
- textView.setTextColor(mTextColor);
- final int padding = 24;
- textView.setPadding(padding, padding, padding, padding);
- //textView.setCompoundDrawablePadding(padding);
- return textView;
}
@Override
@@ -107,17 +64,29 @@ class DeviceListAdapter extends BaseAdapter implements Observer {
notifyDataSetChanged();
}
- private @ColorInt int getColor(Context context, int attr) {
- final TypedArray a = context.obtainStyledAttributes(new TypedValue().data,
- new int[] { attr });
- final int color = a.getColor(0, 0);
- a.recycle();
- return color;
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ final View view = convertView != null
+ ? convertView
+ : LayoutInflater.from(mContext).inflate(R.layout.list_item_device, parent, false);
+
+ final DeviceFilterPair<?> item = getItem(position);
+ bindView(view, item);
+
+ return view;
}
- private static Drawable getTintedIcon(Resources resources, int drawableRes) {
- Drawable icon = resources.getDrawable(drawableRes, null);
- icon.setTint(Color.DKGRAY);
- return icon;
+ private void bindView(@NonNull View view, DeviceFilterPair<?> item) {
+ final TextView textView = view.findViewById(android.R.id.text1);
+ textView.setText(item.getDisplayName());
+
+ final ImageView iconView = view.findViewById(android.R.id.icon);
+
+ // TODO(b/211417476): Set either Bluetooth or WiFi icon.
+ iconView.setVisibility(View.GONE);
+ // final int iconRes = isBt ? android.R.drawable.stat_sys_data_bluetooth
+ // : com.android.internal.R.drawable.ic_wifi_signal_3;
+ // final Drawable icon = getTintedIcon(mResources, iconRes);
+ // iconView.setImageDrawable(icon);
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index 837629911ccd..0d15dffae92d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -17,8 +17,6 @@ package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -46,6 +44,7 @@ import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
+import java.util.Objects;
/**
* This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
@@ -86,6 +85,7 @@ public final class IpSecManager {
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int DIRECTION_FWD = 2;
/**
@@ -988,7 +988,7 @@ public final class IpSecManager {
*/
public IpSecManager(Context ctx, IIpSecService service) {
mContext = ctx;
- mService = checkNotNull(service, "missing service");
+ mService = Objects.requireNonNull(service, "missing service");
}
private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
index b48c1fdaf1b2..36199a046cfc 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
@@ -33,7 +33,6 @@ import android.os.ServiceSpecificException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -41,6 +40,7 @@ import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
+import java.util.Objects;
/**
* This class represents a transform, which roughly corresponds to an IPsec Security Association.
@@ -255,7 +255,7 @@ public final class IpSecTransform implements AutoCloseable {
@NonNull
public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
// TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setEncryption(algo);
return this;
}
@@ -270,7 +270,7 @@ public final class IpSecTransform implements AutoCloseable {
@NonNull
public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
// TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setAuthentication(algo);
return this;
}
@@ -290,7 +290,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
@NonNull
public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setAuthenticatedEncryption(algo);
return this;
}
@@ -311,7 +311,7 @@ public final class IpSecTransform implements AutoCloseable {
@NonNull
public IpSecTransform.Builder setIpv4Encapsulation(
@NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
- Preconditions.checkNotNull(localSocket);
+ Objects.requireNonNull(localSocket);
mConfig.setEncapType(ENCAP_ESPINUDP);
if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
@@ -348,8 +348,8 @@ public final class IpSecTransform implements AutoCloseable {
@NonNull IpSecManager.SecurityParameterIndex spi)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
- Preconditions.checkNotNull(sourceAddress);
- Preconditions.checkNotNull(spi);
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
}
@@ -387,8 +387,8 @@ public final class IpSecTransform implements AutoCloseable {
@NonNull IpSecManager.SecurityParameterIndex spi)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
- Preconditions.checkNotNull(sourceAddress);
- Preconditions.checkNotNull(spi);
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
}
@@ -404,7 +404,7 @@ public final class IpSecTransform implements AutoCloseable {
* @param context current context
*/
public Builder(@NonNull Context context) {
- Preconditions.checkNotNull(context);
+ Objects.requireNonNull(context);
mContext = context;
mConfig = new IpSecConfig();
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
index 3885a9e6d5cb..591605d952e9 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
@@ -24,7 +24,7 @@ import static android.net.TrafficStats.UID_TETHERING;
import android.Manifest;
import android.annotation.IntDef;
import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -32,8 +32,6 @@ import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
-import com.android.server.LocalServices;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -109,8 +107,7 @@ public final class NetworkStatsAccess {
/** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
public static @NetworkStatsAccess.Level int checkAccessLevel(
Context context, int callingUid, String callingPackage) {
- final DevicePolicyManagerInternal dpmi = LocalServices.getService(
- DevicePolicyManagerInternal.class);
+ final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
final TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
boolean hasCarrierPrivileges;
@@ -123,8 +120,9 @@ public final class NetworkStatsAccess {
Binder.restoreCallingIdentity(token);
}
- final boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid);
+ final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
final int appId = UserHandle.getAppId(callingUid);
+
if (hasCarrierPrivileges || isDeviceOwner
|| appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) {
// Carrier-privileged apps and device owners, and the system (including the
@@ -139,8 +137,8 @@ public final class NetworkStatsAccess {
}
//TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
- boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid)
- || dpmi.isActiveDeviceOwner(callingUid));
+ boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
+ || mDpm.isDeviceOwnerApp(callingPackage));
if (isProfileOwner) {
// Apps with the AppOps permission, profile owners, and apps with the privileged
// permission can access data usage for all apps in this user/profile.
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index cb5a025278b1..d85291318fa4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -47,6 +47,7 @@ import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
import com.android.net.module.util.NetworkIdentityUtils;
@@ -58,6 +59,9 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
/**
* Predicate used to match {@link NetworkIdentity}, usually when collecting
@@ -119,20 +123,30 @@ public final class NetworkTemplate implements Parcelable {
public @interface SubscriberIdMatchRule{}
/**
* Value of the match rule of the subscriberId to match networks with specific subscriberId.
+ *
+ * @hide
*/
public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0;
/**
* Value of the match rule of the subscriberId to match networks with any subscriberId which
* includes null and non-null.
+ *
+ * @hide
*/
public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1;
+ // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL.
+ /** @hide */
+ public static final String WIFI_NETWORKID_ALL = null;
+
/**
- * Wi-Fi Network ID is never supposed to be null (if it is, it is a bug that
+ * Wi-Fi Network Key is never supposed to be null (if it is, it is a bug that
* should be fixed), so it's not possible to want to match null vs
- * non-null. Therefore it's fine to use null as a sentinel for Network ID.
+ * non-null. Therefore it's fine to use null as a sentinel for Wifi Network Key.
+ *
+ * @hide
*/
- public static final String WIFI_NETWORKID_ALL = null;
+ public static final String WIFI_NETWORK_KEY_ALL = WIFI_NETWORKID_ALL;
/**
* Include all network types when filtering. This is meant to merge in with the
@@ -278,7 +292,10 @@ public final class NetworkTemplate implements Parcelable {
* Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID,
* and IMSI.
*
- * Call with {@link #WIFI_NETWORKID_ALL} for {@code networkId} to get result regardless of SSID.
+ * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code networkId} to get result regardless
+ * of SSID.
+ *
+ * @hide
*/
public static NetworkTemplate buildTemplateWifi(@Nullable String networkId,
@Nullable String subscriberId) {
@@ -345,6 +362,7 @@ public final class NetworkTemplate implements Parcelable {
*/
private final String[] mMatchSubscriberIds;
+ // TODO: Change variable name to match the Api surface.
private final String mNetworkId;
// Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
@@ -361,14 +379,14 @@ public final class NetworkTemplate implements Parcelable {
// Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
private final int mOemManaged;
- private void checkValidSubscriberIdMatchRule() {
- switch (mMatchRule) {
+ private static void checkValidSubscriberIdMatchRule(int matchRule, int subscriberIdMatchRule) {
+ switch (matchRule) {
case MATCH_MOBILE:
case MATCH_CARRIER:
// MOBILE and CARRIER templates must always specify a subscriber ID.
- if (mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
- throw new IllegalArgumentException("Invalid SubscriberIdMatchRule"
- + "on match rule: " + getMatchRuleName(mMatchRule));
+ if (subscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
+ throw new IllegalArgumentException("Invalid SubscriberIdMatchRule "
+ + "on match rule: " + getMatchRuleName(matchRule));
}
return;
default:
@@ -421,7 +439,7 @@ public final class NetworkTemplate implements Parcelable {
mSubType = subType;
mOemManaged = oemManaged;
mSubscriberIdMatchRule = subscriberIdMatchRule;
- checkValidSubscriberIdMatchRule();
+ checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
if (!isKnownMatchRule(matchRule)) {
throw new IllegalArgumentException("Unknown network template rule " + matchRule
+ " will not match any identity.");
@@ -519,7 +537,7 @@ public final class NetworkTemplate implements Parcelable {
return false;
}
- private String subscriberIdMatchRuleToString(int rule) {
+ private static String subscriberIdMatchRuleToString(int rule) {
switch (rule) {
case SUBSCRIBER_ID_MATCH_RULE_EXACT:
return "EXACT_MATCH";
@@ -542,52 +560,58 @@ public final class NetworkTemplate implements Parcelable {
}
/**
- * Check if the template can be persisted into disk.
- *
- * @hide
+ * Get match rule of the template. See {@code MATCH_*}.
*/
- // TODO: Move to the NetworkPolicy.
- public boolean isPersistable() {
+ @UnsupportedAppUsage
+ public int getMatchRule() {
+ // Wildcard rules are not exposed. For external callers, convert wildcard rules to
+ // exposed rules before returning.
switch (mMatchRule) {
case MATCH_MOBILE_WILDCARD:
+ return MATCH_MOBILE;
case MATCH_WIFI_WILDCARD:
- return false;
- case MATCH_CARRIER:
- return mSubscriberId != null;
- case MATCH_WIFI:
- if (Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
- && mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
- return false;
- }
- return true;
+ return MATCH_WIFI;
default:
- return true;
+ return mMatchRule;
}
}
/**
- * Get match rule of the template. See {@code MATCH_*}.
+ * Get subscriber Id of the template.
*/
+ @Nullable
@UnsupportedAppUsage
- public int getMatchRule() {
- return mMatchRule;
+ public String getSubscriberId() {
+ return mSubscriberId;
}
/**
- * Get subscriber Id of the template.
+ * Get set of subscriber Ids of the template.
+ */
+ @NonNull
+ public Set<String> getSubscriberIds() {
+ return new ArraySet<>(Arrays.asList(mMatchSubscriberIds));
+ }
+
+ /**
+ * Get Wifi Network Key of the template. See {@link WifiInfo#getCurrentNetworkKey()}.
*/
@Nullable
- @UnsupportedAppUsage
- public String getSubscriberId() {
- return mSubscriberId;
+ public String getWifiNetworkKey() {
+ return mNetworkId;
}
+ /** @hide */
+ // TODO: Remove this and replace all callers with {@link #getWifiNetworkKey()}.
+ @Nullable
public String getNetworkId() {
return mNetworkId;
}
/**
* Get Subscriber Id Match Rule of the template.
+ *
+ * @hide
*/
public int getSubscriberIdMatchRule() {
return mSubscriberIdMatchRule;
@@ -602,6 +626,38 @@ public final class NetworkTemplate implements Parcelable {
}
/**
+ * Get roaming filter of the template.
+ */
+ @NetworkStats.Roaming
+ public int getRoaming() {
+ return mRoaming;
+ }
+
+ /**
+ * Get the default network status filter of the template.
+ */
+ @NetworkStats.DefaultNetwork
+ public int getDefaultNetworkStatus() {
+ return mDefaultNetwork;
+ }
+
+ /**
+ * Get the Radio Access Technology(RAT) type filter of the template.
+ */
+ public int getRatType() {
+ return mSubType;
+ }
+
+ /**
+ * Get the OEM managed filter of the template. See {@code OEM_MANAGED_*} or
+ * {@code android.net.NetworkIdentity#OEM_*}.
+ */
+ @OemManaged
+ public int getOemManaged() {
+ return mOemManaged;
+ }
+
+ /**
* Test if given {@link NetworkIdentity} matches this template.
*
* @hide
@@ -680,10 +736,10 @@ public final class NetworkTemplate implements Parcelable {
/**
* Check if network with matching SSID. Returns true when the SSID matches, or when
- * {@code mNetworkId} is {@code WIFI_NETWORKID_ALL}.
+ * {@code mNetworkId} is {@code WIFI_NETWORK_KEY_ALL}.
*/
private boolean matchesWifiNetworkId(@Nullable String networkId) {
- return Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
+ return Objects.equals(mNetworkId, WIFI_NETWORK_KEY_ALL)
|| Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId));
}
@@ -948,4 +1004,184 @@ public final class NetworkTemplate implements Parcelable {
return new NetworkTemplate[size];
}
};
+
+ /**
+ * Builder class for NetworkTemplate.
+ */
+ public static final class Builder {
+ private final int mMatchRule;
+ // Use a SortedSet to provide a deterministic order when fetching the first one.
+ @NonNull
+ private final SortedSet<String> mMatchSubscriberIds = new TreeSet<>();
+ @Nullable
+ private String mWifiNetworkKey;
+
+ // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
+ private int mMetered;
+ private int mRoaming;
+ private int mDefaultNetwork;
+ private int mRatType;
+
+ // Bitfield containing OEM network properties {@code NetworkIdentity#OEM_*}.
+ private int mOemManaged;
+
+ /**
+ * Creates a new Builder with given match rule to construct NetworkTemplate objects.
+ *
+ * @param matchRule the match rule of the template, see {@code MATCH_*}.
+ */
+ public Builder(@TemplateMatchRule final int matchRule) {
+ assertRequestableMatchRule(matchRule);
+ // Initialize members with default values.
+ mMatchRule = matchRule;
+ mWifiNetworkKey = WIFI_NETWORK_KEY_ALL;
+ mMetered = METERED_ALL;
+ mRoaming = ROAMING_ALL;
+ mDefaultNetwork = DEFAULT_NETWORK_ALL;
+ mRatType = NETWORK_TYPE_ALL;
+ mOemManaged = OEM_MANAGED_ALL;
+ }
+
+ /**
+ * Set the Subscriber Ids. Calling this function with an empty set represents
+ * the intention of matching any Subscriber Ids.
+ *
+ * @param subscriberIds the list of Subscriber Ids.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubscriberIds(@NonNull Set<String> subscriberIds) {
+ Objects.requireNonNull(subscriberIds);
+ mMatchSubscriberIds.clear();
+ mMatchSubscriberIds.addAll(subscriberIds);
+ return this;
+ }
+
+ /**
+ * Set the Wifi Network Key.
+ *
+ * @param wifiNetworkKey the Wifi Network Key, see {@link WifiInfo#getCurrentNetworkKey()}.
+ * Or null to match all networks.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
+ mWifiNetworkKey = wifiNetworkKey;
+ return this;
+ }
+
+ /**
+ * Set the meteredness filter.
+ *
+ * @param metered the meteredness filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setMeteredness(@NetworkStats.Meteredness int metered) {
+ mMetered = metered;
+ return this;
+ }
+
+ /**
+ * Set the roaming filter.
+ *
+ * @param roaming the roaming filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRoaming(@NetworkStats.Roaming int roaming) {
+ mRoaming = roaming;
+ return this;
+ }
+
+ /**
+ * Set the default network status filter.
+ *
+ * @param defaultNetwork the default network status filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setDefaultNetworkStatus(@NetworkStats.DefaultNetwork int defaultNetwork) {
+ mDefaultNetwork = defaultNetwork;
+ return this;
+ }
+
+ /**
+ * Set the Radio Access Technology(RAT) type filter.
+ *
+ * @param ratType the Radio Access Technology(RAT) type filter. Use
+ * {@link #NETWORK_TYPE_ALL} to include all network types when filtering.
+ * See {@code TelephonyManager.NETWORK_TYPE_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRatType(@NetworkType int ratType) {
+ // Input will be validated with the match rule when building the template.
+ mRatType = ratType;
+ return this;
+ }
+
+ /**
+ * Set the OEM managed filter.
+ *
+ * @param oemManaged the match rule to match different type of OEM managed network or
+ * unmanaged networks. See {@code OEM_MANAGED_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setOemManaged(@OemManaged int oemManaged) {
+ mOemManaged = oemManaged;
+ return this;
+ }
+
+ /**
+ * Check whether the match rule is requestable.
+ *
+ * @param matchRule the target match rule to be checked.
+ */
+ private static void assertRequestableMatchRule(final int matchRule) {
+ if (!isKnownMatchRule(matchRule)
+ || matchRule == MATCH_PROXY
+ || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_WIFI_WILDCARD) {
+ throw new IllegalArgumentException("Invalid match rule: "
+ + getMatchRuleName(matchRule));
+ }
+ }
+
+ private void assertRequestableParameters() {
+ // TODO: Check all the input are legitimate.
+ }
+
+ /**
+ * For backward compatibility, deduce match rule to a wildcard match rule
+ * if the Subscriber Ids are empty.
+ */
+ private int getWildcardDeducedMatchRule() {
+ if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) {
+ return MATCH_MOBILE_WILDCARD;
+ } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty()
+ && mWifiNetworkKey == WIFI_NETWORK_KEY_ALL) {
+ return MATCH_WIFI_WILDCARD;
+ }
+ return mMatchRule;
+ }
+
+ /**
+ * Builds the instance of the NetworkTemplate.
+ *
+ * @return the built instance of NetworkTemplate.
+ */
+ @NonNull
+ public NetworkTemplate build() {
+ assertRequestableParameters();
+ final int subscriberIdMatchRule = mMatchSubscriberIds.isEmpty()
+ ? SUBSCRIBER_ID_MATCH_RULE_ALL : SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ return new NetworkTemplate(getWildcardDeducedMatchRule(),
+ mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(),
+ mMatchSubscriberIds.toArray(new String[0]),
+ mWifiNetworkKey, mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged,
+ subscriberIdMatchRule);
+ }
+ }
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8e20e023f6e0..47b0744497ff 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1334,7 +1334,7 @@
<string name="notice_header" translatable="false"></string>
<!-- Name of the phone device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name">Phone speaker</string>
+ <string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
<string name="media_transfer_this_phone">This phone</string>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index cdf274f23dbb..dec3245a102f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1348,7 +1348,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
Settings.Global.CONNECTIVITY_CHANGE_DELAY,
Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED,
Settings.Global.CAPTIVE_PORTAL_SERVER,
- Settings.Global.NSD_ON,
Settings.Global.SET_INSTALL_LOCATION,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 6072f68e3691..38a258f6ecf0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1100,10 +1100,6 @@ class SettingsProtoDumpUtil {
p.end(notificationToken);
dumpSetting(s, p,
- Settings.Global.NSD_ON,
- GlobalSettingsProto.NSD_ON);
-
- dumpSetting(s, p,
Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
GlobalSettingsProto.NR_NSA_TRACKING_SCREEN_OFF_MODE);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 2d1f0cb8e7c4..ed813a0b1f46 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -391,7 +391,6 @@ public class SettingsBackupTest {
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
Settings.Global.NOTIFICATION_FEEDBACK_ENABLED,
Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
- Settings.Global.NSD_ON,
Settings.Global.NTP_SERVER,
Settings.Global.NTP_TIMEOUT,
Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a3f07d8fd1b4..10252ee3bc60 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -610,6 +610,9 @@
<!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
<uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
+
<!-- Permission required for CTS test - CommunalManagerTest -->
<uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
<uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 177f69590882..11199358b6ef 100644
--- a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -17,14 +17,14 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:paddingMode="stack"
- android:paddingStart="44dp"
+ android:paddingStart="24dp"
android:paddingEnd="44dp"
android:paddingLeft="0dp"
android:paddingRight="0dp">
<item>
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface" />
- <corners android:radius="@dimen/keyguard_user_switcher_corner" />
+ <corners android:radius="32dp" />
</shape>
</item>
<item
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml
new file mode 100644
index 000000000000..5bb5690b8f34
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+-->
+
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ <corners android:radius="24dp" />
+</shape>
diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml
index 96a2d1534ebe..74ece15aa78c 100644
--- a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml
@@ -18,5 +18,5 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface" />
- <corners android:radius="@dimen/keyguard_user_switcher_popup_corner" />
+ <corners android:radius="28dp" />
</shape>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 4f0925f3bfbb..36035fc87e40 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -30,8 +30,8 @@
<ImageView
android:id="@+id/user_icon"
- android:layout_width="@dimen/keyguard_user_switcher_icon_size"
- android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
+ android:layout_width="@dimen/bouncer_user_switcher_icon_size"
+ android:layout_height="@dimen/bouncer_user_switcher_icon_size" />
<!-- need to keep this outer view in order to have a correctly sized anchor
for the dropdown menu, as well as dropdown background in the right place -->
@@ -40,13 +40,12 @@
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_marginTop="30dp"
- android:minHeight="48dp">
+ android:layout_marginTop="30dp">
<TextView
- style="@style/Keyguard.UserSwitcher.Spinner.Header"
+ style="@style/Bouncer.UserSwitcher.Spinner.Header"
android:clickable="false"
android:id="@+id/user_switcher_header"
- android:layout_width="@dimen/keyguard_user_switcher_width"
+ android:layout_width="@dimen/bouncer_user_switcher_width"
android:layout_height="wrap_content" />
</LinearLayout>>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
index b08e1ff4c472..c388f15be6e4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
@@ -13,13 +13,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<TextView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Keyguard.UserSwitcher.Spinner.Item"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:paddingStart="@dimen/control_menu_horizontal_padding"
- android:paddingEnd="@dimen/control_menu_horizontal_padding"
- android:textDirection="locale"/>
-
+ android:layout_height="wrap_content">
+ <TextView
+ style="@style/Bouncer.UserSwitcher.Spinner.Item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp" />
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 2819dc9c27b7..c8bb8e99be17 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -108,11 +108,15 @@
<dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
- <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen>
- <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen>
- <dimen name="keyguard_user_switcher_width">320dp</dimen>
- <dimen name="keyguard_user_switcher_icon_size">310dp</dimen>
- <dimen name="keyguard_user_switcher_corner">32dp</dimen>
- <dimen name="keyguard_user_switcher_popup_corner">24dp</dimen>
- <dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen>
+ <dimen name="bouncer_user_switcher_header_text_size">20sp</dimen>
+ <dimen name="bouncer_user_switcher_item_text_size">20sp</dimen>
+ <dimen name="bouncer_user_switcher_item_line_height">24sp</dimen>
+ <dimen name="bouncer_user_switcher_item_icon_size">28dp</dimen>
+ <dimen name="bouncer_user_switcher_item_icon_padding">12dp</dimen>
+ <dimen name="bouncer_user_switcher_width">248dp</dimen>
+ <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
+ <dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
+ <dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
+ <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
+ <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 60f034ae7ee8..5048f856ac70 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -141,22 +141,23 @@
<item name="android:shadowRadius">0</item>
</style>
- <style name="Keyguard.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView">
+ <style name="Bouncer.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
- <item name="android:paddingTop">@dimen/keyguard_user_switcher_item_padding_vertical</item>
- <item name="android:paddingBottom">@dimen/keyguard_user_switcher_item_padding_vertical</item>
+ <item name="android:minHeight">48dp</item>
+ <item name="android:paddingVertical">@dimen/bouncer_user_switcher_item_padding_vertical</item>
+ <item name="android:paddingHorizontal">@dimen/bouncer_user_switcher_item_padding_horizontal</item>
+ <item name="android:lineHeight">@dimen/bouncer_user_switcher_item_line_height</item>
+ <item name="android:gravity">start|center_vertical</item>
</style>
- <style name="Keyguard.UserSwitcher.Spinner.Header">
- <item name="android:background">@drawable/keyguard_user_switcher_header_bg</item>
- <item name="android:textSize">@dimen/keyguard_user_switcher_header_text_size</item>
+ <style name="Bouncer.UserSwitcher.Spinner.Header">
+ <item name="android:background">@drawable/bouncer_user_switcher_header_bg</item>
+ <item name="android:textSize">@dimen/bouncer_user_switcher_header_text_size</item>
</style>
- <style name="Keyguard.UserSwitcher.Spinner.Item">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textSize">@dimen/keyguard_user_switcher_item_text_size</item>
+ <style name="Bouncer.UserSwitcher.Spinner.Item">
+ <item name="android:textSize">@dimen/bouncer_user_switcher_item_text_size</item>
</style>
</resources>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 6fbc41c7e20d..2d082dc7d5e2 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -26,6 +26,13 @@
android:gravity="center_vertical"
>
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/app_icon"
+ android:layout_width="@dimen/media_ttt_icon_size"
+ android:layout_height="@dimen/media_ttt_icon_size"
+ android:layout_marginEnd="12dp"
+ />
+
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
@@ -37,8 +44,8 @@
<ProgressBar
android:id="@+id/loading"
android:indeterminate="true"
- android:layout_width="@dimen/media_ttt_icon_size"
- android:layout_height="@dimen/media_ttt_icon_size"
+ android:layout_width="@dimen/media_ttt_loading_size"
+ android:layout_height="@dimen/media_ttt_loading_size"
android:layout_marginStart="12dp"
android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
style="?android:attr/progressBarStyleSmall"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 843c69fb1f49..b7f25e81046c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -980,7 +980,8 @@
<!-- Media tap-to-transfer chip -->
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
<dimen name="media_ttt_text_size">16sp</dimen>
- <dimen name="media_ttt_icon_size">16dp</dimen>
+ <dimen name="media_ttt_icon_size">24dp</dimen>
+ <dimen name="media_ttt_loading_size">20dp</dimen>
<dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
<dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b84cb19b9468..b5ea498f185d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,9 +32,14 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
@@ -70,6 +75,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.UserIcons;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -760,11 +766,11 @@ public class KeyguardSecurityContainer extends FrameLayout {
private ViewGroup mView;
private ViewGroup mUserSwitcherViewGroup;
private KeyguardSecurityViewFlipper mViewFlipper;
- private ImageView mUserIconView;
private TextView mUserSwitcher;
private FalsingManager mFalsingManager;
private UserSwitcherController mUserSwitcherController;
private KeyguardUserSwitcherPopupMenu mPopup;
+ private Resources mResources;
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@@ -775,6 +781,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
mViewFlipper = viewFlipper;
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
+ mResources = v.getContext().getResources();
if (mUserSwitcherViewGroup == null) {
LayoutInflater.from(v.getContext()).inflate(
@@ -784,9 +791,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
}
- mUserIconView = mView.findViewById(R.id.user_icon);
- Drawable icon = UserIcons.getDefaultUserIcon(v.getContext().getResources(), 0, false);
- mUserIconView.setImageDrawable(icon);
+ Drawable userIcon = findUserIcon(KeyguardUpdateMonitor.getCurrentUser());
+ ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon);
updateSecurityViewLocation();
@@ -802,6 +808,14 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
}
+ private Drawable findUserIcon(int userId) {
+ Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId);
+ if (userIcon != null) {
+ return new BitmapDrawable(userIcon);
+ }
+ return UserIcons.getDefaultUserIcon(mResources, userId, false);
+ }
+
@Override
public void startAppearAnimation(SecurityMode securityMode) {
// IME insets animations handle alpha and translation
@@ -824,8 +838,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
return;
}
- int yTranslation = mView.getContext().getResources().getDimensionPixelSize(
- R.dimen.disappear_y_translation);
+ int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
AnimatorSet anims = new AnimatorSet();
ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
@@ -840,21 +853,70 @@ public class KeyguardSecurityContainer extends FrameLayout {
String currentUserName = mUserSwitcherController.getCurrentUserName();
mUserSwitcher.setText(currentUserName);
+ final UserRecord currentUser = getCurrentUser();
ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UserRecord item = getItem(position);
- TextView view = (TextView) convertView;
+ FrameLayout view = (FrameLayout) convertView;
if (view == null) {
- view = (TextView) LayoutInflater.from(parent.getContext()).inflate(
+ view = (FrameLayout) LayoutInflater.from(parent.getContext()).inflate(
R.layout.keyguard_bouncer_user_switcher_item,
parent,
false);
}
- view.setText(getName(parent.getContext(), item));
+ TextView textView = (TextView) view.getChildAt(0);
+ textView.setText(getName(parent.getContext(), item));
+ Drawable icon = null;
+ if (item.picture != null) {
+ icon = new BitmapDrawable(item.picture);
+ } else {
+ icon = getDrawable(item, view.getContext());
+ }
+ int iconSize = view.getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_item_icon_size);
+ int iconPadding = view.getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_item_icon_padding);
+ icon.setBounds(0, 0, iconSize, iconSize);
+ textView.setCompoundDrawablePadding(iconPadding);
+ textView.setCompoundDrawablesRelative(icon, null, null, null);
+
+ if (item == currentUser) {
+ textView.setBackground(view.getContext().getDrawable(
+ R.drawable.bouncer_user_switcher_item_selected_bg));
+ } else {
+ textView.setBackground(null);
+ }
return view;
}
+
+ private Drawable getDrawable(UserRecord item, Context context) {
+ Drawable drawable;
+ if (item.isCurrent && item.isGuest) {
+ drawable = context.getDrawable(R.drawable.ic_avatar_guest_user);
+ } else {
+ drawable = getIconDrawable(context, item);
+ }
+
+ int iconColor;
+ if (item.isSwitchToEnabled) {
+ iconColor = Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.colorAccentPrimaryVariant);
+ } else {
+ iconColor = context.getResources().getColor(
+ R.color.kg_user_switcher_restricted_avatar_icon_color,
+ context.getTheme());
+ }
+ drawable.setTint(iconColor);
+
+ Drawable bg = context.getDrawable(R.drawable.kg_bg_avatar);
+ bg.setTintBlendMode(BlendMode.DST);
+ bg.setTint(Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.colorSurfaceVariant));
+ drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+ return drawable;
+ }
};
if (adapter.getCount() < 2) {
@@ -876,7 +938,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void onItemClick(AdapterView parent, View view, int pos, long id) {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
- UserRecord user = adapter.getItem(pos);
+ // Subtract one for the header
+ UserRecord user = adapter.getItem(pos - 1);
if (!user.isCurrent) {
adapter.onUserListItemClicked(user);
}
@@ -888,6 +951,16 @@ public class KeyguardSecurityContainer extends FrameLayout {
});
}
+ private UserRecord getCurrentUser() {
+ for (int i = 0; i < mUserSwitcherController.getUsers().size(); ++i) {
+ UserRecord userRecord = mUserSwitcherController.getUsers().get(i);
+ if (userRecord.isCurrent) {
+ return userRecord;
+ }
+ }
+ return null;
+ }
+
/**
* Each view will get half the width. Yes, it would be easier to use something other than
* FrameLayout but it was too disruptive to downstream projects to change.
@@ -901,8 +974,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override
public void updateSecurityViewLocation() {
- if (mView.getContext().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT) {
+ if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
mUserSwitcherViewGroup.setTranslationY(0);
@@ -912,8 +984,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
// Attempt to reposition a bit higher to make up for this frame being a bit lower
// on the device
- int yTrans = mView.getContext().getResources().getDimensionPixelSize(
- R.dimen.status_bar_height);
+ int yTrans = mResources.getDimensionPixelSize(R.dimen.status_bar_height);
mUserSwitcherViewGroup.setTranslationY(-yTrans);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index 7b6ce3e1c951..efa5558f5088 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -18,6 +18,8 @@ package com.android.keyguard;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.ShapeDrawable;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListPopupWindow;
@@ -32,15 +34,6 @@ import com.android.systemui.plugins.FalsingManager;
public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
private Context mContext;
private FalsingManager mFalsingManager;
- private int mLastHeight = -1;
- private View.OnLayoutChangeListener mLayoutListener = (v, l, t, r, b, ol, ot, or, ob) -> {
- int height = -v.getMeasuredHeight() + getAnchorView().getHeight();
- if (height != mLastHeight) {
- mLastHeight = height;
- setVerticalOffset(height);
- KeyguardUserSwitcherPopupMenu.super.show();
- }
- };
public KeyguardUserSwitcherPopupMenu(@NonNull Context context,
@NonNull FalsingManager falsingManager) {
@@ -49,7 +42,7 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
mFalsingManager = falsingManager;
Resources res = mContext.getResources();
setBackgroundDrawable(
- res.getDrawable(R.drawable.keyguard_user_switcher_popup_bg, context.getTheme()));
+ res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme()));
setModal(true);
setOverlapAnchor(true);
}
@@ -63,8 +56,20 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
super.show();
ListView listView = getListView();
- // This will force the popupwindow to show upward instead of drop down
- listView.addOnLayoutChangeListener(mLayoutListener);
+ listView.setVerticalScrollBarEnabled(false);
+ listView.setHorizontalScrollBarEnabled(false);
+
+ // Creates a transparent spacer between items
+ ShapeDrawable shape = new ShapeDrawable();
+ shape.setAlpha(0);
+ listView.setDivider(shape);
+ listView.setDividerHeight(mContext.getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_divider_height));
+
+ int height = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_header_height);
+ listView.addHeaderView(createSpacer(height), null, false);
+ listView.addFooterView(createSpacer(height), null, false);
listView.setOnTouchListener((v, ev) -> {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -72,11 +77,19 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
}
return false;
});
+ super.show();
}
- @Override
- public void dismiss() {
- getListView().removeOnLayoutChangeListener(mLayoutListener);
- super.dismiss();
+ private View createSpacer(int height) {
+ return new View(mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(1, height);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+ };
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 90a1e5e64daf..f82ea790bb64 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -80,11 +80,6 @@ class AuthRippleController @Inject constructor(
private var circleReveal: LightRevealEffect? = null
private var udfpsController: UdfpsController? = null
-
- private var dwellScale = 2f
- private var expandedDwellScale = 2.5f
- private var aodDwellScale = 1.9f
- private var aodExpandedDwellScale = 2.3f
private var udfpsRadius: Float = -1f
override fun onInit() {
@@ -128,7 +123,7 @@ class AuthRippleController @Inject constructor(
updateSensorLocation()
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) {
- mView.setSensorLocation(fingerprintSensorLocation!!)
+ mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius)
showUnlockedRipple()
} else if (biometricSourceType == BiometricSourceType.FACE &&
faceSensorLocation != null) {
@@ -241,24 +236,12 @@ class AuthRippleController @Inject constructor(
}
private fun updateRippleColor() {
- mView.setColor(
- Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
+ mView.setLockScreenColor(Utils.getColorAttrDefaultColor(sysuiContext,
+ R.attr.wallpaperTextColorAccent))
}
private fun showDwellRipple() {
- if (statusBarStateController.isDozing) {
- mView.startDwellRipple(
- /* startRadius */ udfpsRadius,
- /* endRadius */ udfpsRadius * aodDwellScale,
- /* expandedRadius */ udfpsRadius * aodExpandedDwellScale,
- /* isDozing */ true)
- } else {
- mView.startDwellRipple(
- /* startRadius */ udfpsRadius,
- /* endRadius */ udfpsRadius * dwellScale,
- /* expandedRadius */ udfpsRadius * expandedDwellScale,
- /* isDozing */ false)
- }
+ mView.startDwellRipple(statusBarStateController.isDozing)
}
private val keyguardUpdateMonitorCallback =
@@ -295,7 +278,7 @@ class AuthRippleController @Inject constructor(
return
}
- mView.setSensorLocation(fingerprintSensorLocation!!)
+ mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius)
showDwellRipple()
}
@@ -307,8 +290,8 @@ class AuthRippleController @Inject constructor(
private val authControllerCallback =
object : AuthController.Callback {
override fun onAllAuthenticatorsRegistered() {
- updateSensorLocation()
updateUdfpsDependentParams()
+ updateSensorLocation()
}
override fun onEnrollmentsChanged() {
@@ -329,20 +312,6 @@ class AuthRippleController @Inject constructor(
}
inner class AuthRippleCommand : Command {
- fun printLockScreenDwellInfo(pw: PrintWriter) {
- pw.println("lock screen dwell ripple: " +
- "\n\tsensorLocation=$fingerprintSensorLocation" +
- "\n\tdwellScale=$dwellScale" +
- "\n\tdwellExpand=$expandedDwellScale")
- }
-
- fun printAodDwellInfo(pw: PrintWriter) {
- pw.println("aod dwell ripple: " +
- "\n\tsensorLocation=$fingerprintSensorLocation" +
- "\n\tdwellScale=$aodDwellScale" +
- "\n\tdwellExpand=$aodExpandedDwellScale")
- }
-
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.isEmpty()) {
invalidCommand(pw)
@@ -350,11 +319,9 @@ class AuthRippleController @Inject constructor(
when (args[0]) {
"dwell" -> {
showDwellRipple()
- if (statusBarStateController.isDozing) {
- printAodDwellInfo(pw)
- } else {
- printLockScreenDwellInfo(pw)
- }
+ pw.println("lock screen dwell ripple: " +
+ "\n\tsensorLocation=$fingerprintSensorLocation" +
+ "\n\tudfpsRadius=$udfpsRadius")
}
"fingerprint" -> {
updateSensorLocation()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c6d26ffb9957..d67363079e17 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -21,6 +21,7 @@ import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
@@ -28,6 +29,7 @@ import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.charging.DwellRippleShader
import com.android.systemui.statusbar.charging.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
@@ -43,23 +45,32 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
- private val dwellPulseDuration = 50L
- private val dwellAlphaDuration = dwellPulseDuration
- private val dwellAlpha: Float = 1f
- private val dwellExpandDuration = 1200L - dwellPulseDuration
+ private val dwellPulseDuration = 100L
+ private val dwellExpandDuration = 2000L - dwellPulseDuration
- private val aodDwellPulseDuration = 50L
- private var aodDwellAlphaDuration = aodDwellPulseDuration
- private var aodDwellAlpha: Float = .8f
- private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
+ private var drawDwell: Boolean = false
+ private var drawRipple: Boolean = false
+ private var lockScreenColorVal = Color.WHITE
private val retractDuration = 400L
private var alphaInDuration: Long = 0
private var unlockedRippleInProgress: Boolean = false
+ private val dwellShader = DwellRippleShader()
+ private val dwellPaint = Paint()
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
private var retractAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null
+ private var dwellRadius: Float = 0f
+ set(value) {
+ dwellShader.maxRadius = value
+ field = value
+ }
+ private var dwellOrigin: PointF = PointF()
+ set(value) {
+ dwellShader.origin = value
+ field = value
+ }
private var radius: Float = 0f
set(value) {
rippleShader.radius = value
@@ -76,6 +87,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
+
+ dwellShader.color = 0xffffffff.toInt() // default color
+ dwellShader.progress = 0f
+ dwellShader.distortionStrength = .4f
+ dwellPaint.shader = dwellShader
visibility = GONE
}
@@ -84,6 +100,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
}
+ fun setFingerprintSensorLocation(location: PointF, sensorRadius: Float) {
+ origin = location
+ radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
+ dwellOrigin = location
+ dwellRadius = sensorRadius * 1.5f
+ }
+
fun setAlphaInDuration(duration: Long) {
alphaInDuration = duration
}
@@ -97,14 +120,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
if (dwellPulseOutAnimator?.isRunning == true) {
- val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f)
+ val retractRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f)
.apply {
interpolator = retractInterpolator
duration = retractDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
- rippleShader.progress = animator.animatedValue as Float
- rippleShader.time = now.toFloat()
+ dwellShader.progress = animator.animatedValue as Float
+ dwellShader.time = now.toFloat()
invalidate()
}
@@ -114,8 +137,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
interpolator = Interpolators.LINEAR
duration = retractDuration
addUpdateListener { animator ->
- rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
+ dwellShader.color = ColorUtils.setAlphaComponent(
+ dwellShader.color,
animator.animatedValue as Int
)
invalidate()
@@ -127,13 +150,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
dwellPulseOutAnimator?.cancel()
- rippleShader.shouldFadeOutRipple = false
- visibility = VISIBLE
+ drawDwell = true
}
override fun onAnimationEnd(animation: Animator?) {
- visibility = GONE
- resetRippleAlpha()
+ drawDwell = false
+ resetDwellAlpha()
}
})
start()
@@ -142,101 +164,54 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
/**
- * Ripple that moves animates from an outer ripple ring of
- * startRadius => endRadius => expandedRadius
+ * Plays a ripple animation that grows to the dwellRadius with distortion.
*/
- fun startDwellRipple(
- startRadius: Float,
- endRadius: Float,
- expandedRadius: Float,
- isDozing: Boolean
- ) {
+ fun startDwellRipple(isDozing: Boolean) {
if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
return
}
- // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer
- // ring see RippleShader
- val startDwellProgress = startRadius / radius / 4f
- val endInitialDwellProgress = endRadius / radius / 4f
- val endExpandDwellProgress = expandedRadius / radius / 4f
-
- val alpha = if (isDozing) aodDwellAlpha else dwellAlpha
- val pulseOutEndAlpha = (255 * alpha).toInt()
- val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255)
- val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
- endInitialDwellProgress).apply {
- interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
- addUpdateListener { animator ->
- val now = animator.currentPlayTime
- rippleShader.progress = animator.animatedValue as Float
- rippleShader.time = now.toFloat()
-
- invalidate()
- }
- }
+ updateDwellRippleColor(isDozing)
- val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
+ val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(0f, .8f).apply {
interpolator = Interpolators.LINEAR
- duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
+ duration = dwellPulseDuration
addUpdateListener { animator ->
- rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- animator.animatedValue as Int
- )
+ val now = animator.currentPlayTime
+ dwellShader.progress = animator.animatedValue as Float
+ dwellShader.time = now.toFloat()
+
invalidate()
}
}
// slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
- val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
- endExpandDwellProgress).apply {
+ val expandDwellRippleAnimator = ValueAnimator.ofFloat(.8f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+ duration = dwellExpandDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
- rippleShader.progress = animator.animatedValue as Float
- rippleShader.time = now.toFloat()
+ dwellShader.progress = animator.animatedValue as Float
+ dwellShader.time = now.toFloat()
invalidate()
}
}
- val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
- .apply {
- interpolator = Interpolators.LINEAR
- duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
- addUpdateListener { animator ->
- rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- animator.animatedValue as Int
- )
- invalidate()
- }
- }
-
- val initialDwellPulseOutAnimator = AnimatorSet().apply {
- playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator)
- }
- val expandDwellAnimator = AnimatorSet().apply {
- playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator)
- }
-
dwellPulseOutAnimator = AnimatorSet().apply {
playSequentially(
- initialDwellPulseOutAnimator,
- expandDwellAnimator
+ dwellPulseOutRippleAnimator,
+ expandDwellRippleAnimator
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
retractAnimator?.cancel()
- rippleShader.shouldFadeOutRipple = false
visibility = VISIBLE
+ drawDwell = true
}
override fun onAnimationEnd(animation: Animator?) {
- visibility = GONE
+ drawDwell = false
resetRippleAlpha()
}
})
@@ -252,16 +227,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
return // Ignore if ripple effect is already playing
}
- var rippleStart = 0f
- var alphaDuration = alphaInDuration
- if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) {
- rippleStart = rippleShader.progress
- alphaDuration = 0
- dwellPulseOutAnimator?.cancel()
- retractAnimator?.cancel()
- }
-
- val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
+ val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
addUpdateListener { animator ->
@@ -274,7 +240,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
- duration = alphaDuration
+ duration = alphaInDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
@@ -293,12 +259,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
override fun onAnimationStart(animation: Animator?) {
unlockedRippleInProgress = true
rippleShader.shouldFadeOutRipple = true
+ drawRipple = true
visibility = VISIBLE
}
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run()
unlockedRippleInProgress = false
+ drawRipple = false
visibility = GONE
}
})
@@ -313,17 +281,42 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
)
}
- fun setColor(color: Int) {
- rippleShader.color = color
+ fun setLockScreenColor(color: Int) {
+ lockScreenColorVal = color
+ rippleShader.color = lockScreenColorVal
resetRippleAlpha()
}
+ fun updateDwellRippleColor(isDozing: Boolean) {
+ if (isDozing) {
+ dwellShader.color = Color.WHITE
+ } else {
+ dwellShader.color = lockScreenColorVal
+ }
+ resetDwellAlpha()
+ }
+
+ fun resetDwellAlpha() {
+ dwellShader.color = ColorUtils.setAlphaComponent(
+ dwellShader.color,
+ 255
+ )
+ }
+
override fun onDraw(canvas: Canvas?) {
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 2f
- canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+ if (drawDwell) {
+ val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
+ (1 - dwellShader.progress)) * dwellRadius * 2f
+ canvas?.drawCircle(dwellOrigin.x, dwellOrigin.y, maskRadius, dwellPaint)
+ }
+
+ if (drawRipple) {
+ val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * radius * 2f
+ canvas?.drawCircle(origin.x, origin.y, mask, ripplePaint)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 9c25b3596be6..2beed4c6a7e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -40,7 +40,6 @@ public interface DozeHost {
void extendPulse(int reason);
void setAnimateWakeup(boolean animateWakeup);
- void setAnimateScreenOff(boolean animateScreenOff);
/**
* Reports that a tap event happend on the Sensors Low Power Island.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index b2fe3bb94dd3..e568b8282856 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -21,30 +21,21 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import android.app.AlarmManager;
import android.content.Context;
-import android.content.res.Configuration;
import android.os.Handler;
import android.os.SystemClock;
-import android.provider.Settings;
import android.text.format.Formatter;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.unfold.FoldAodAnimationController;
-import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.WakeLock;
import java.util.Calendar;
-import java.util.Optional;
import javax.inject.Inject;
@@ -52,9 +43,7 @@ import javax.inject.Inject;
* The policy controlling doze.
*/
@DozeScope
-public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
- ConfigurationController.ConfigurationListener, FoldAodAnimationStatus,
- StatusBarStateController.StateListener {
+public class DozeUi implements DozeMachine.Part {
// if enabled, calls dozeTimeTick() whenever the time changes:
private static final boolean BURN_IN_TESTING_ENABLED = false;
private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
@@ -62,26 +51,15 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
private final DozeHost mHost;
private final Handler mHandler;
private final WakeLock mWakeLock;
- private final FoldAodAnimationController mFoldAodAnimationController;
private DozeMachine mMachine;
private final AlarmTimeout mTimeTicker;
private final boolean mCanAnimateTransition;
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
private final StatusBarStateController mStatusBarStateController;
- private final TunerService mTunerService;
- private final ConfigurationController mConfigurationController;
-
- private boolean mKeyguardShowing;
private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
- updateAnimateScreenOff();
- }
-
- @Override
public void onTimeChanged() {
if (BURN_IN_TESTING_ENABLED && mStatusBarStateController.isDozing()) {
// update whenever the time changes for manual burn in testing
@@ -91,11 +69,6 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
mHandler.post(mWakeLock.wrap(() -> {}));
}
}
-
- @Override
- public void onShadeExpandedChanged(boolean expanded) {
- updateAnimateScreenOff();
- }
};
private long mLastTimeTickElapsed = 0;
@@ -104,10 +77,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
public DozeUi(Context context, AlarmManager alarmManager,
WakeLock wakeLock, DozeHost host, @Main Handler handler,
DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
- DozeLog dozeLog, TunerService tunerService,
StatusBarStateController statusBarStateController,
- Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
- ConfigurationController configurationController) {
+ DozeLog dozeLog) {
mContext = context;
mWakeLock = wakeLock;
mHost = host;
@@ -117,31 +88,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
mDozeLog = dozeLog;
- mTunerService = tunerService;
mStatusBarStateController = statusBarStateController;
- mStatusBarStateController.addCallback(this);
-
- mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON);
-
- mConfigurationController = configurationController;
- mConfigurationController.addCallback(this);
-
- mFoldAodAnimationController = sysUiUnfoldComponent
- .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
-
- if (mFoldAodAnimationController != null) {
- mFoldAodAnimationController.addCallback(this);
- }
- }
-
- @Override
- public void destroy() {
- mTunerService.removeTunable(this);
- mConfigurationController.removeCallback(this);
-
- if (mFoldAodAnimationController != null) {
- mFoldAodAnimationController.removeCallback(this);
- }
}
@Override
@@ -149,22 +96,6 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
mMachine = dozeMachine;
}
- /**
- * Decide if we're taking over the screen-off animation
- * when the device was configured to skip doze after screen off.
- */
- private void updateAnimateScreenOff() {
- if (mCanAnimateTransition) {
- final boolean controlScreenOff =
- mDozeParameters.getAlwaysOn()
- && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff())
- && !mHost.isPowerSaveActive();
- mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
- mHost.setAnimateScreenOff(controlScreenOff
- && mDozeParameters.shouldAnimateDozingChange());
- }
- }
-
private void pulseWhileDozing(int reason) {
mHost.pulseWhileDozing(
new DozeHost.PulseCallback() {
@@ -293,34 +224,4 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
scheduleTimeTick();
}
-
- @VisibleForTesting
- KeyguardUpdateMonitorCallback getKeyguardCallback() {
- return mKeyguardVisibilityCallback;
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) {
- updateAnimateScreenOff();
- }
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateAnimateScreenOff();
- }
-
- /**
- * Called when StatusBar state changed, could affect unlocked screen off animation state
- */
- @Override
- public void onStatePostChange() {
- updateAnimateScreenOff();
- }
-
- @Override
- public void onFoldToAodAnimationChanged() {
- updateAnimateScreenOff();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 1174fa8e4061..a398a7fa4575 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -99,12 +99,13 @@ public interface MediaModule {
static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper(
MediaTttFlags mediaTttFlags,
CommandRegistry commandRegistry,
+ Context context,
MediaTttChipController mediaTttChipController,
@Main DelayableExecutor mainExecutor) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
return Optional.of(new MediaTttCommandLineHelper(
- commandRegistry, mediaTttChipController, mainExecutor));
+ commandRegistry, context, mediaTttChipController, mainExecutor));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
index baa469d43e22..2b55d634b382 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -25,6 +25,7 @@ import android.view.View
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
+import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -74,6 +75,11 @@ class MediaTttChipController @Inject constructor(
}
val currentChipView = chipView!!
+ // App icon
+ currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
+ this.setImageDrawable(chipState.appIconDrawable)
+ }
+
// Text
currentChipView.requireViewById<TextView>(R.id.text).apply {
text = context.getString(chipState.chipText, chipState.otherDeviceName)
@@ -128,7 +134,11 @@ class MediaTttChipController @Inject constructor(
val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS)
// Make UI changes on the main thread
mainExecutor.execute {
- displayChip(TransferSucceeded(chipState.otherDeviceName, undoRunnable))
+ displayChip(
+ TransferSucceeded(
+ chipState.otherDeviceName, chipState.appIconDrawable, undoRunnable
+ )
+ )
}
} catch (ex: Exception) {
// TODO(b/203800327): Maybe show a failure chip here if UX decides we need one.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
index 1f308a9a6cd9..62e3b1ac8d5c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.taptotransfer
+import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
import com.android.systemui.R
import java.util.concurrent.Future
@@ -26,12 +27,15 @@ import java.util.concurrent.Future
*
* This is a sealed class where each subclass represents a specific chip state. Each subclass can
* contain additional information that is necessary for only that state.
+ *
+ * @property chipText a string resource for the text that the chip should display.
+ * @property otherDeviceName the name of the other device involved in the transfer.
+ * @property appIconDrawable a drawable representing the icon of the app playing the media.
*/
sealed class MediaTttChipState(
- /** A string resource for the text that the chip should display. */
@StringRes internal val chipText: Int,
- /** The name of the other device involved in the transfer. */
- internal val otherDeviceName: String
+ internal val otherDeviceName: String,
+ internal val appIconDrawable: Drawable,
)
/**
@@ -39,8 +43,9 @@ sealed class MediaTttChipState(
* The chip will instruct the user to move closer in order to initiate the transfer.
*/
class MoveCloserToTransfer(
- otherDeviceName: String
-) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName)
+ otherDeviceName: String,
+ appIconDrawable: Drawable
+) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName, appIconDrawable)
/**
* A state representing that a transfer has been initiated (but not completed).
@@ -52,8 +57,9 @@ class MoveCloserToTransfer(
*/
class TransferInitiated(
otherDeviceName: String,
+ appIconDrawable: Drawable,
val future: Future<Runnable?>
-) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
+) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
/**
* A state representing that a transfer has been successfully completed.
@@ -63,5 +69,6 @@ class TransferInitiated(
*/
class TransferSucceeded(
otherDeviceName: String,
+ appIconDrawable: Drawable,
val undoRunnable: Runnable? = null
-) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
+) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 663037cc850f..74983e5bfe03 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.taptotransfer
+import android.content.Context
+import android.graphics.drawable.Icon
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.commandline.Command
@@ -34,9 +37,13 @@ import javax.inject.Inject
@SysUISingleton
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
+ context: Context,
private val mediaTttChipController: MediaTttChipController,
@Main private val mainExecutor: DelayableExecutor,
) {
+ private val appIconDrawable =
+ Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+
init {
commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
@@ -47,19 +54,21 @@ class MediaTttCommandLineHelper @Inject constructor(
val otherDeviceName = args[0]
when (args[1]) {
MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
- mediaTttChipController.displayChip(MoveCloserToTransfer(otherDeviceName))
+ mediaTttChipController.displayChip(
+ MoveCloserToTransfer(otherDeviceName, appIconDrawable)
+ )
}
TRANSFER_INITIATED_COMMAND_NAME -> {
val futureTask = FutureTask { fakeUndoRunnable }
mediaTttChipController.displayChip(
- TransferInitiated(otherDeviceName, futureTask)
+ TransferInitiated(otherDeviceName, appIconDrawable, futureTask)
)
mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
}
TRANSFER_SUCCEEDED_COMMAND_NAME -> {
mediaTttChipController.displayChip(
- TransferSucceeded(otherDeviceName, fakeUndoRunnable)
+ TransferSucceeded(otherDeviceName, appIconDrawable, fakeUndoRunnable)
)
}
else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 03d8e7e03c0f..c8115e2c197a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -92,6 +92,8 @@ class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffe
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
+ scrim.interpolatedRevealAmount = interpolatedAmount
+
scrim.startColorAlpha =
getPercentPastThreshold(1 - interpolatedAmount,
threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
@@ -152,6 +154,7 @@ class CircleReveal(
// non-interpolated amount
val fadeAmount = getPercentPastThreshold(amount, 0.5f)
val radius = startRadius + ((endRadius - startRadius) * amount)
+ scrim.interpolatedRevealAmount = amount
scrim.revealGradientEndColorAlpha = 1f - fadeAmount
scrim.setRevealGradientBounds(
centerX - radius /* left */,
@@ -182,6 +185,7 @@ class PowerButtonReveal(
with(scrim) {
revealGradientEndColorAlpha = 1f - fadeAmount
+ interpolatedRevealAmount = interpolatedAmount
setRevealGradientBounds(
width * (1f + OFF_SCREEN_START_AMOUNT) -
width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
@@ -284,6 +288,14 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
}
}
+ var interpolatedRevealAmount: Float = 1f
+
+ val isScrimAlmostOccludes: Boolean
+ get() {
+ // if the interpolatedRevealAmount less than 0.1, over 90% of the screen is black.
+ return interpolatedRevealAmount < 0.1f
+ }
+
private fun updateScrimOpaque() {
isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
index a1d086b5d768..73d3e2afac7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
@@ -59,8 +59,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
}
vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) {
- return p + vec2(sin(p.x * frequency + in_phase1),
- cos(p.y * frequency * 1.23 + in_phase2)) * distort_amount_xy;
+ return p + vec2(sin(p.y * frequency + in_phase1),
+ cos(p.x * frequency * -1.23 + in_phase2)) * distort_amount_xy;
}
vec4 ripple(vec2 p, float distort_xy, float frequency) {
@@ -73,11 +73,11 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
"""
private const val SHADER_MAIN = """vec4 main(vec2 p) {
vec4 color1 = ripple(p,
- 12 * in_distortion_strength, // distort_xy
+ 34 * in_distortion_strength, // distort_xy
0.012 // frequency
);
vec4 color2 = ripple(p,
- 17.5 * in_distortion_strength, // distort_xy
+ 49 * in_distortion_strength, // distort_xy
0.018 // frequency
);
// Alpha blend between two layers.
@@ -128,8 +128,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
set(value) {
field = value * 0.001f
setUniform("in_time", field)
- setUniform("in_phase1", field * 2f + 0.367f)
- setUniform("in_phase2", field * 5.2f * 1.531f)
+ setUniform("in_phase1", field * 3f + 0.367f)
+ setUniform("in_phase2", field * 7.2f * 1.531f)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index d87a02493a23..1b42b58a55aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
@@ -26,7 +27,10 @@ import android.util.Log;
import android.util.MathUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -36,13 +40,18 @@ import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@@ -54,7 +63,8 @@ import javax.inject.Inject;
public class DozeParameters implements
TunerService.Tunable,
com.android.systemui.plugins.statusbar.DozeParameters,
- Dumpable {
+ Dumpable, ConfigurationController.ConfigurationListener,
+ StatusBarStateController.StateListener, FoldAodAnimationController.FoldAodAnimationStatus {
private static final int MAX_DURATION = 60 * 1000;
public static final boolean FORCE_NO_BLANKING =
SystemProperties.getBoolean("debug.force_no_blanking", false);
@@ -69,12 +79,30 @@ public class DozeParameters implements
private final BatteryController mBatteryController;
private final FeatureFlags mFeatureFlags;
private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final FoldAodAnimationController mFoldAodAnimationController;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final Set<Callback> mCallbacks = new HashSet<>();
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
+ private boolean mKeyguardShowing;
+ @VisibleForTesting
+ final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mKeyguardShowing = showing;
+ updateControlScreenOff();
+ }
+
+ @Override
+ public void onShadeExpandedChanged(boolean expanded) {
+ updateControlScreenOff();
+ }
+ };
+
@Inject
protected DozeParameters(
@Main Resources resources,
@@ -85,7 +113,12 @@ public class DozeParameters implements
TunerService tunerService,
DumpManager dumpManager,
FeatureFlags featureFlags,
- ScreenOffAnimationController screenOffAnimationController) {
+ ScreenOffAnimationController screenOffAnimationController,
+ Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ ConfigurationController configurationController,
+ StatusBarStateController statusBarStateController) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -97,11 +130,22 @@ public class DozeParameters implements
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
mFeatureFlags = featureFlags;
mScreenOffAnimationController = screenOffAnimationController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
this,
Settings.Secure.DOZE_ALWAYS_ON,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ configurationController.addCallback(this);
+ statusBarStateController.addCallback(this);
+
+ mFoldAodAnimationController = sysUiUnfoldComponent
+ .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+
+ if (mFoldAodAnimationController != null) {
+ mFoldAodAnimationController.addCallback(this);
+ }
}
public boolean getDisplayStateSupported() {
@@ -222,13 +266,26 @@ public class DozeParameters implements
mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation);
}
+ public void updateControlScreenOff() {
+ if (!getDisplayNeedsBlanking()) {
+ final boolean controlScreenOff =
+ getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ setControlScreenOffAnimation(controlScreenOff);
+ }
+ }
+
/**
* Whether we want to control the screen off animation when the device is unlocked. If we do,
* we'll animate in AOD before turning off the screen, rather than simply fading to black and
* then abruptly showing AOD.
+ *
+ * There are currently several reasons we might not want to control the screen off even if we
+ * are able to, such as the shade being expanded, being in landscape, or having animations
+ * disabled for a11y.
*/
public boolean shouldControlUnlockedScreenOff() {
- return mScreenOffAnimationController.shouldControlUnlockedScreenOff();
+ return canControlUnlockedScreenOff()
+ && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
}
public boolean shouldDelayKeyguardShow() {
@@ -325,6 +382,11 @@ public class DozeParameters implements
@Override
public void onTuningChanged(String key, String newValue) {
mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+
+ if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) {
+ updateControlScreenOff();
+ }
+
for (Callback callback : mCallbacks) {
callback.onAlwaysOnChange();
}
@@ -332,6 +394,21 @@ public class DozeParameters implements
}
@Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateControlScreenOff();
+ }
+
+ @Override
+ public void onStatePostChange() {
+ updateControlScreenOff();
+ }
+
+ @Override
+ public void onFoldToAodAnimationChanged() {
+ updateControlScreenOff();
+ }
+
+ @Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn());
pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 57b9c03ce576..a88a3b6392c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -63,7 +63,6 @@ public final class DozeServiceHost implements DozeHost {
private final DozeLog mDozeLog;
private final PowerManager mPowerManager;
private boolean mAnimateWakeup;
- private boolean mAnimateScreenOff;
private boolean mIgnoreTouchWhilePulsing;
private Runnable mPendingScreenOffCallback;
@VisibleForTesting
@@ -357,11 +356,6 @@ public final class DozeServiceHost implements DozeHost {
}
@Override
- public void setAnimateScreenOff(boolean animateScreenOff) {
- mAnimateScreenOff = animateScreenOff;
- }
-
- @Override
public void onSlpiTap(float screenX, float screenY) {
if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
&& mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
@@ -440,10 +434,6 @@ public final class DozeServiceHost implements DozeHost {
return mAnimateWakeup;
}
- boolean shouldAnimateScreenOff() {
- return mAnimateScreenOff;
- }
-
boolean getIgnoreTouchWhilePulsing() {
return mIgnoreTouchWhilePulsing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 07914cf1d9c4..2ba70df8a1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3162,7 +3162,7 @@ public class StatusBar extends CoreStartable implements
boolean wakeAndUnlock = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
- || (mDozing && mDozeServiceHost.shouldAnimateScreenOff()
+ || (mDozing && mDozeParameters.shouldControlScreenOff()
&& visibleNotOccludedOrWillBe);
mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index fc661b920837..0ba713464b80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -9,6 +9,9 @@ import android.os.Handler
import android.provider.Settings
import android.view.Surface
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
@@ -50,7 +53,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
private val keyguardStateController: KeyguardStateController,
private val dozeParameters: dagger.Lazy<DozeParameters>,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val interactionJankMonitor: InteractionJankMonitor
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private val handler = Handler()
@@ -78,17 +82,28 @@ class UnlockedScreenOffAnimationController @Inject constructor(
sendUnlockedScreenOffProgressUpdate(
1f - (it.animatedFraction as Float),
1f - (it.animatedValue as Float))
+ if (lightRevealScrim.isScrimAlmostOccludes &&
+ interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)) {
+ // ends the instrument when the scrim almost occludes the screen.
+ // because the following janky frames might not be perceptible.
+ interactionJankMonitor.end(CUJ_SCREEN_OFF)
+ }
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
lightRevealScrim.revealAmount = 1f
lightRevealAnimationPlaying = false
sendUnlockedScreenOffProgressUpdate(0f, 0f)
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF)
}
override fun onAnimationEnd(animation: Animator?) {
lightRevealAnimationPlaying = false
}
+
+ override fun onAnimationStart(animation: Animator?) {
+ interactionJankMonitor.begin(statusBar.notificationShadeWindowView, CUJ_SCREEN_OFF)
+ }
})
}
@@ -163,6 +178,16 @@ class UnlockedScreenOffAnimationController @Inject constructor(
decidedToAnimateGoingToSleep = null
// We need to unset the listener. These are persistent for future animators
keyguardView.animate().setListener(null)
+ interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
+
+ override fun onAnimationCancel(animation: Animator?) {
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
+
+ override fun onAnimationStart(animation: Animator?) {
+ interactionJankMonitor.begin(
+ statusBar.notificationShadeWindowView, CUJ_SCREEN_OFF_SHOW_AOD)
}
})
.start()
@@ -229,10 +254,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return false
}
- if (!dozeParameters.get().canControlUnlockedScreenOff()) {
- return false
- }
-
// If animations are disabled system-wide, don't play this one either.
if (Settings.Global.getString(
context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") {
@@ -248,10 +269,10 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// already expanded and showing notifications/QS, the animation looks really messy. For now,
// disable it if the notification panel is not fully collapsed.
if ((!this::statusBar.isInitialized ||
- !statusBar.notificationPanelViewController.isFullyCollapsed)
+ !statusBar.notificationPanelViewController.isFullyCollapsed) &&
// Status bar might be expanded because we have started
// playing the animation already
- && !isAnimationPlaying()
+ !isAnimationPlaying()
) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 8e8a33fd0d9b..3831857c5c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static com.android.settingslib.Utils.updateLocationEnabled;
@@ -30,11 +32,13 @@ import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.appops.AppOpItem;
import com.android.systemui.appops.AppOpsController;
@@ -43,6 +47,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -59,30 +64,47 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private final Context mContext;
private final AppOpsController mAppOpsController;
+ private final DeviceConfigProxy mDeviceConfigProxy;
private final BootCompleteCache mBootCompleteCache;
private final UserTracker mUserTracker;
private final H mHandler;
private boolean mAreActiveLocationRequests;
+ private boolean mShouldDisplayAllAccesses;
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
+ DeviceConfigProxy deviceConfigProxy,
@Main Looper mainLooper, @Background Handler backgroundHandler,
BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
UserTracker userTracker) {
mContext = context;
mAppOpsController = appOpsController;
+ mDeviceConfigProxy = deviceConfigProxy;
mBootCompleteCache = bootCompleteCache;
mHandler = new H(mainLooper);
mUserTracker = userTracker;
+ mShouldDisplayAllAccesses = getDeviceConfigSetting();
+
+ // Register to listen for changes in DeviceConfig settings.
+ mDeviceConfigProxy.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ backgroundHandler::post,
+ properties -> {
+ mShouldDisplayAllAccesses = getDeviceConfigSetting();
+ updateActiveLocationRequests();
+ });
// Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.MODE_CHANGED_ACTION);
broadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, UserHandle.ALL);
- mAppOpsController.addCallback(new int[]{OP_MONITOR_HIGH_POWER_LOCATION}, this);
+ // Listen to all accesses and filter the ones interested in based on flags.
+ mAppOpsController.addCallback(
+ new int[]{OP_COARSE_LOCATION, OP_FINE_LOCATION, OP_MONITOR_HIGH_POWER_LOCATION},
+ this);
// Examine the current location state and initialize the status view.
backgroundHandler.post(this::updateActiveLocationRequests);
@@ -154,6 +176,11 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
UserHandle.of(userId));
}
+ private boolean getDeviceConfigSetting() {
+ return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false);
+ }
+
/**
* Returns true if there currently exist active high power location requests.
*/
@@ -171,10 +198,37 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
return false;
}
- // Reads the active location requests and updates the status view if necessary.
+ /**
+ * Returns true if there currently exist active location requests.
+ */
+ @VisibleForTesting
+ protected boolean areActiveLocationRequests() {
+ if (!mShouldDisplayAllAccesses) {
+ return false;
+ }
+ List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps();
+
+ final int numItems = appOpsItems.size();
+ for (int i = 0; i < numItems; i++) {
+ if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION
+ || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION,
+ // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary.
private void updateActiveLocationRequests() {
boolean hadActiveLocationRequests = mAreActiveLocationRequests;
- mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
+ if (mShouldDisplayAllAccesses) {
+ mAreActiveLocationRequests =
+ areActiveHighPowerLocationRequests() || areActiveLocationRequests();
+ } else {
+ mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
+ }
if (mAreActiveLocationRequests != hadActiveLocationRequests) {
mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index e6fc49fae315..0b89ef28d227 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.hardware.input.InputManager
import android.hardware.display.DisplayManager
import android.os.Handler
import android.os.Trace
@@ -195,8 +196,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor(
params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
params.fitInsetsTypes = 0
- params.flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
params.setTrustedOverlay()
val packageName: String = context.opPackageName
@@ -239,6 +239,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor(
if (scrimView == null) {
addView()
}
+ // Disable input dispatching during transition.
+ InputManager.getInstance().cancelCurrentTouch()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 4600bc71459a..31b17f8dd11d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -258,11 +258,6 @@ public final class WMShell extends CoreStartable
public void onKeyguardVisibilityChanged(boolean showing) {
splitScreen.onKeyguardVisibilityChanged(showing);
}
-
- @Override
- public void onKeyguardOccludedChanged(boolean occluded) {
- splitScreen.onKeyguardOccludedChanged(occluded);
- }
};
mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
@@ -271,11 +266,6 @@ public final class WMShell extends CoreStartable
public void onFinishedWakingUp() {
splitScreen.onFinishedWakingUp();
}
-
- @Override
- public void onFinishedGoingToSleep() {
- splitScreen.onFinishedGoingToSleep();
- }
});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 2c4808a4b84f..5128ccc15d9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -131,7 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */)
// THEN update sensor location and show ripple
- verify(rippleView).setSensorLocation(fpsLocation)
+ verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f)
verify(rippleView).startUnlockedRipple(any())
}
@@ -292,10 +292,10 @@ class AuthRippleControllerTest : SysuiTestCase() {
reset(rippleView)
captor.value.onThemeChanged()
- verify(rippleView).setColor(ArgumentMatchers.anyInt())
+ verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
reset(rippleView)
captor.value.onUiModeChanged()
- verify(rippleView).setColor(ArgumentMatchers.anyInt())
+ verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 55af51d3fddf..e5a75e231f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -27,8 +27,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,10 +41,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.unfold.FoldAodAnimationController;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.After;
@@ -56,8 +51,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DozeUiTest extends SysuiTestCase {
@@ -82,12 +75,6 @@ public class DozeUiTest extends SysuiTestCase {
private DozeUi mDozeUi;
@Mock
private StatusBarStateController mStatusBarStateController;
- @Mock
- private FoldAodAnimationController mFoldAodAnimationController;
- @Mock
- private SysUIUnfoldComponent mSysUIUnfoldComponent;
- @Mock
- private ConfigurationController mConfigurationController;
@Before
public void setUp() throws Exception {
@@ -98,13 +85,8 @@ public class DozeUiTest extends SysuiTestCase {
mWakeLock = new WakeLockFake();
mHandler = mHandlerThread.getThreadHandler();
- when(mSysUIUnfoldComponent.getFoldAodAnimationController())
- .thenReturn(mFoldAodAnimationController);
-
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
- mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
- mConfigurationController);
+ mDozeParameters, mKeyguardUpdateMonitor, mStatusBarStateController, mDozeLog);
mDozeUi.setDozeMachine(mMachine);
}
@@ -116,7 +98,7 @@ public class DozeUiTest extends SysuiTestCase {
}
@Test
- public void pausingAndUnpausingAod_registersTimeTickAfterUnpausing() throws Exception {
+ public void pausingAndUnpausingAod_registersTimeTickAfterUnpausing() {
mDozeUi.transitionTo(UNINITIALIZED, INITIALIZED);
mDozeUi.transitionTo(INITIALIZED, DOZE_AOD);
mDozeUi.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
@@ -129,60 +111,9 @@ public class DozeUiTest extends SysuiTestCase {
}
@Test
- public void propagatesAnimateScreenOff_noAlwaysOn() {
- reset(mHost);
- when(mDozeParameters.getAlwaysOn()).thenReturn(false);
- when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
-
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
- verify(mHost).setAnimateScreenOff(eq(false));
- }
-
- @Test
- public void propagatesAnimateScreenOff_alwaysOn() {
- reset(mHost);
- when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
-
- // Take over when the keyguard is visible.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
- verify(mHost).setAnimateScreenOff(eq(true));
-
- // Do not animate screen-off when keyguard isn't visible - PowerManager will do it.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
- verify(mHost).setAnimateScreenOff(eq(false));
- }
-
- @Test
- public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() {
- reset(mHost);
- when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false);
-
- // Take over when the keyguard is visible.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
- verify(mHost).setAnimateScreenOff(eq(false));
- }
-
- @Test
- public void neverAnimateScreenOff_whenNotSupported() {
- // Re-initialize DozeParameters saying that the display requires blanking.
- reset(mDozeParameters);
- reset(mHost);
- when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
- mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
- mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
- mConfigurationController);
- mDozeUi.setDozeMachine(mMachine);
-
- // Never animate if display doesn't support it.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
- verify(mHost, never()).setAnimateScreenOff(eq(false));
+ public void transitionSetsAnimateWakeup_noAlwaysOn() {
+ mDozeUi.transitionTo(UNINITIALIZED, DOZE);
+ verify(mHost).setAnimateWakeup(eq(false));
}
@Test
@@ -192,49 +123,4 @@ public class DozeUiTest extends SysuiTestCase {
mDozeUi.transitionTo(UNINITIALIZED, DOZE);
verify(mHost).setAnimateWakeup(eq(true));
}
-
- @Test
- public void keyguardVisibility_changesControlScreenOffAnimation() {
- // Pre-condition
- reset(mDozeParameters);
- when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
-
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
- verify(mDozeParameters).setControlScreenOffAnimation(eq(false));
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
- verify(mDozeParameters).setControlScreenOffAnimation(eq(true));
- }
-
- @Test
- public void transitionSetsAnimateWakeup_noAlwaysOn() {
- mDozeUi.transitionTo(UNINITIALIZED, DOZE);
- verify(mHost).setAnimateWakeup(eq(false));
- }
-
- @Test
- public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() {
- when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
-
- // Tell doze that keyguard is not visible.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
-
- // Since we're controlling the unlocked screen off animation, verify that we've asked to
- // control the screen off animation despite being unlocked.
- verify(mDozeParameters).setControlScreenOffAnimation(true);
- }
-
- @Test
- public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() {
- when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
-
- // Tell doze that keyguard is not visible.
- mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
-
- // Since we're not controlling the unlocked screen off animation, verify that we haven't
- // asked to control the screen off animation since we're unlocked.
- verify(mDozeParameters).setControlScreenOffAnimation(false);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
index 87499172fc46..de6525a2e750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.taptotransfer
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
import android.view.View
import android.view.WindowManager
+import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.test.filters.SmallTest
@@ -36,10 +39,11 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Future
@SmallTest
class MediaTttChipControllerTest : SysuiTestCase() {
-
+ private lateinit var appIconDrawable: Drawable
private lateinit var fakeMainClock: FakeSystemClock
private lateinit var fakeMainExecutor: FakeExecutor
private lateinit var fakeBackgroundClock: FakeSystemClock
@@ -53,6 +57,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
fakeMainClock = FakeSystemClock()
fakeMainExecutor = FakeExecutor(fakeMainClock)
fakeBackgroundClock = FakeSystemClock()
@@ -64,24 +69,24 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun displayChip_chipAdded() {
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ mediaTttChipController.displayChip(moveCloserToTransfer())
verify(windowManager).addView(any(), any())
}
@Test
fun displayChip_twice_chipNotAddedTwice() {
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ mediaTttChipController.displayChip(moveCloserToTransfer())
reset(windowManager)
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ mediaTttChipController.displayChip(moveCloserToTransfer())
verify(windowManager, never()).addView(any(), any())
}
@Test
fun removeChip_chipRemoved() {
// First, add the chip
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ mediaTttChipController.displayChip(moveCloserToTransfer())
// Then, remove it
mediaTttChipController.removeChip()
@@ -97,24 +102,26 @@ class MediaTttChipControllerTest : SysuiTestCase() {
}
@Test
- fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ mediaTttChipController.displayChip(moveCloserToTransfer())
val chipView = getChipView()
+ assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferInitiated_futureNotResolvedYet_loadingIcon_noUndo() {
+ fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+ mediaTttChipController.displayChip(transferInitiated(future))
// Don't resolve the future in any way and don't run our executors
// Assert we're still in the loading state
val chipView = getChipView()
+ assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -125,7 +132,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
val undoRunnable = Runnable { }
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+ mediaTttChipController.displayChip(transferInitiated(future))
future.set(undoRunnable)
fakeBackgroundExecutor.advanceClockToLast()
@@ -146,7 +153,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
fun transferInitiated_futureCancelled_chipRemoved() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+ mediaTttChipController.displayChip(transferInitiated(future))
future.cancel(true)
fakeBackgroundExecutor.advanceClockToLast()
@@ -163,7 +170,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+ mediaTttChipController.displayChip(transferInitiated(future))
// We won't set anything on the future, but we will still run the executors so that we're
// waiting on the future resolving. If we have a bug in our code, then this test will time
@@ -180,22 +187,28 @@ class MediaTttChipControllerTest : SysuiTestCase() {
}
@Test
- fun transferSucceededNullUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, undoRunnable = null))
+ fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() {
+ mediaTttChipController.displayChip(transferSucceeded())
val chipView = getChipView()
+ assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferSucceededNullUndoRunnable_noUndo() {
+ mediaTttChipController.displayChip(transferSucceeded(undoRunnable = null))
+
+ val chipView = getChipView()
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferSucceededWithUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_undoWithClick() {
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
+ fun transferSucceededWithUndoRunnable_undoWithClick() {
+ mediaTttChipController.displayChip(transferSucceeded { })
val chipView = getChipView()
- assertThat(chipView.getChipText()).contains(DEVICE_NAME)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
}
@@ -205,7 +218,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
var runnableRun = false
val runnable = Runnable { runnableRun = true }
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, runnable))
+ mediaTttChipController.displayChip(transferSucceeded(undoRunnable = runnable))
getChipView().getUndoButton().performClick()
assertThat(runnableRun).isTrue()
@@ -213,36 +226,39 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
+ mediaTttChipController.displayChip(moveCloserToTransfer())
+ mediaTttChipController.displayChip(transferInitiated())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
}
@Test
fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
+ mediaTttChipController.displayChip(transferInitiated())
+ mediaTttChipController.displayChip(transferSucceeded())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
}
@Test
fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
- mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
+ mediaTttChipController.displayChip(transferInitiated())
+ mediaTttChipController.displayChip(transferSucceeded { })
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
}
@Test
fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
- mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+ mediaTttChipController.displayChip(transferSucceeded())
+ mediaTttChipController.displayChip(moveCloserToTransfer())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
+ private fun LinearLayout.getAppIconDrawable(): Drawable =
+ (this.requireViewById<ImageView>(R.id.app_icon)).drawable
+
private fun LinearLayout.getChipText(): String =
(this.requireViewById<TextView>(R.id.text)).text as String
@@ -256,6 +272,19 @@ class MediaTttChipControllerTest : SysuiTestCase() {
verify(windowManager).addView(viewCaptor.capture(), any())
return viewCaptor.value as LinearLayout
}
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun moveCloserToTransfer() = MoveCloserToTransfer(DEVICE_NAME, appIconDrawable)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferInitiated(
+ future: Future<Runnable?> = TEST_FUTURE
+ ) = TransferInitiated(DEVICE_NAME, appIconDrawable, future)
+
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun transferSucceeded(
+ undoRunnable: Runnable? = null
+ ) = TransferSucceeded(DEVICE_NAME, appIconDrawable, undoRunnable)
}
private const val DEVICE_NAME = "My Tablet"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 9b2d3eb17568..91b529875654 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -49,7 +49,7 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
mediaTttCommandLineHelper =
MediaTttCommandLineHelper(
- commandRegistry, mediaTttChipController, FakeExecutor(FakeSystemClock())
+ commandRegistry, context, mediaTttChipController, FakeExecutor(FakeSystemClock())
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index e4db07220949..a14ea54fc7e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.phone;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
@@ -32,14 +33,19 @@ import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import org.junit.Assert;
import org.junit.Before;
@@ -48,10 +54,11 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DozeParametersTest extends SysuiTestCase {
-
private DozeParameters mDozeParameters;
@Mock Resources mResources;
@@ -63,10 +70,37 @@ public class DozeParametersTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags;
@Mock private DumpManager mDumpManager;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private FoldAodAnimationController mFoldAodAnimationController;
+ @Mock private SysUIUnfoldComponent mSysUIUnfoldComponent;
+ @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private ConfigurationController mConfigurationController;
+
+ /**
+ * The current value of PowerManager's dozeAfterScreenOff property.
+ *
+ * This property controls whether System UI is controlling the screen off animation. If it's
+ * false (PowerManager should not doze after screen off) then System UI is controlling the
+ * animation. If true, we're not controlling it and PowerManager will doze immediately.
+ */
+ private boolean mPowerManagerDozeAfterScreenOff;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
+ // Save the current value set for dozeAfterScreenOff so we can make assertions. This method
+ // is only called if the value changes, which makes it difficult to check that it was set
+ // correctly in tests.
+ doAnswer(invocation -> {
+ mPowerManagerDozeAfterScreenOff = invocation.getArgument(0);
+ return mPowerManagerDozeAfterScreenOff;
+ }).when(mPowerManager).setDozeAfterScreenOff(anyBoolean());
+
+ when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+ .thenReturn(mFoldAodAnimationController);
+
mDozeParameters = new DozeParameters(
mResources,
mAmbientDisplayConfiguration,
@@ -76,23 +110,31 @@ public class DozeParametersTest extends SysuiTestCase {
mTunerService,
mDumpManager,
mFeatureFlags,
- mScreenOffAnimationController
+ mScreenOffAnimationController,
+ Optional.of(mSysUIUnfoldComponent),
+ mUnlockedScreenOffAnimationController,
+ mKeyguardUpdateMonitor,
+ mConfigurationController,
+ mStatusBarStateController
);
+
+ when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true);
+ setAodEnabledForTest(true);
+ setShouldControlUnlockedScreenOffForTest(true);
+ setDisplayNeedsBlankingForTest(false);
}
+
@Test
- public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_false() {
+ public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_correctly() {
+ // If we want to control screen off, we do NOT want PowerManager to doze after screen off.
+ // Obviously.
mDozeParameters.setControlScreenOffAnimation(true);
- reset(mPowerManager);
- mDozeParameters.setControlScreenOffAnimation(false);
- verify(mPowerManager).setDozeAfterScreenOff(eq(true));
- }
+ assertFalse(mPowerManagerDozeAfterScreenOff);
- @Test
- public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_true() {
+ // If we don't want to control screen off, PowerManager is free to doze after screen off if
+ // that's what'll make it happy.
mDozeParameters.setControlScreenOffAnimation(false);
- reset(mPowerManager);
- mDozeParameters.setControlScreenOffAnimation(true);
- verify(mPowerManager).setDozeAfterScreenOff(eq(false));
+ assertTrue(mPowerManagerDozeAfterScreenOff);
}
@Test
@@ -121,35 +163,124 @@ public class DozeParametersTest extends SysuiTestCase {
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
}
+ /**
+ * PowerManager.setDozeAfterScreenOff(true) means we are not controlling screen off, and calling
+ * it with false means we are. Confusing, but sure - make sure that we call PowerManager with
+ * the correct value depending on whether we want to control screen off.
+ */
@Test
public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
- when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
- mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
- when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true);
- when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ // If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
+ // that when that value is updated, we called through to PowerManager.
+ setAodEnabledForTest(false);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ assertTrue(mPowerManagerDozeAfterScreenOff);
- // Trigger the setter for the current value.
- mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
-
- // We should have asked power manager not to doze after screen off no matter what, since
- // we're animating and controlling screen off.
- verify(mPowerManager).setDozeAfterScreenOff(eq(false));
+ // And vice versa...
+ setAodEnabledForTest(true);
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+ assertFalse(mPowerManagerDozeAfterScreenOff);
}
@Test
public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
- when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
- mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+ setShouldControlUnlockedScreenOffForTest(true);
when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
// Trigger the setter for the current value.
mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ }
+
+ @Test
+ public void propagatesAnimateScreenOff_noAlwaysOn() {
+ setAodEnabledForTest(false);
+ setDisplayNeedsBlankingForTest(false);
+
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ }
+
+ @Test
+ public void propagatesAnimateScreenOff_alwaysOn() {
+ setAodEnabledForTest(true);
+ setDisplayNeedsBlankingForTest(false);
+ setShouldControlUnlockedScreenOffForTest(false);
+
+ // Take over when the keyguard is visible.
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+
+ // Do not animate screen-off when keyguard isn't visible.
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ }
+
+
+ @Test
+ public void neverAnimateScreenOff_whenNotSupported() {
+ setDisplayNeedsBlankingForTest(true);
+
+ // Never animate if display doesn't support it.
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ }
+
+
+ @Test
+ public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() {
+ setShouldControlUnlockedScreenOffForTest(true);
+
+ // Tell doze that keyguard is not visible.
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(
+ false /* showing */);
+
+ // Since we're controlling the unlocked screen off animation, verify that we've asked to
+ // control the screen off animation despite being unlocked.
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+ }
+
+
+ @Test
+ public void keyguardVisibility_changesControlScreenOffAnimation() {
+ setShouldControlUnlockedScreenOffForTest(false);
+
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false);
+ assertFalse(mDozeParameters.shouldControlScreenOff());
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+ }
+
+ @Test
+ public void keyguardVisibility_changesControlScreenOffAnimation_respectsUnlockedScreenOff() {
+ setShouldControlUnlockedScreenOffForTest(true);
+
+ // Even if the keyguard is gone, we should control screen off if we can control unlocked
+ // screen off.
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false);
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+
+ mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+ assertTrue(mDozeParameters.shouldControlScreenOff());
+ }
+
+ private void setDisplayNeedsBlankingForTest(boolean needsBlanking) {
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_displayBlanksAfterDoze)).thenReturn(
+ needsBlanking);
+ }
+
+ private void setAodEnabledForTest(boolean enabled) {
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(enabled);
+ mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "");
+ }
- // We should have asked power manager to doze only if we're not controlling screen off
- // normally.
- verify(mPowerManager).setDozeAfterScreenOff(
- eq(!mDozeParameters.shouldControlScreenOff()));
+ private void setShouldControlUnlockedScreenOffForTest(boolean shouldControl) {
+ when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
+ .thenReturn(shouldControl);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index a8a33dabf1aa..24a56bc76fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -59,6 +60,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock
private lateinit var statusBarStateController: StatusBarStateControllerImpl
+ @Mock
+ private lateinit var interactionJankMonitor: InteractionJankMonitor
@Before
fun setUp() {
@@ -71,7 +74,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator },
keyguardStateController,
dagger.Lazy<DozeParameters> { dozeParameters },
- globalSettings
+ globalSettings,
+ interactionJankMonitor
)
controller.initialize(statusbar, lightRevealScrim)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index be836d4132f2..087f2e6006cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -15,9 +15,7 @@
package com.android.systemui.statusbar.policy;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,18 +25,25 @@ import android.content.Intent;
import android.location.LocationManager;
import android.os.Handler;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.appops.AppOpItem;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DeviceConfigProxyFake;
+
+import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +58,7 @@ public class LocationControllerImplTest extends SysuiTestCase {
private LocationControllerImpl mLocationController;
private TestableLooper mTestableLooper;
+ private DeviceConfigProxy mDeviceConfigProxy;
@Mock private AppOpsController mAppOpsController;
@Mock private UserTracker mUserTracker;
@@ -62,15 +68,17 @@ public class LocationControllerImplTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ mDeviceConfigProxy = new DeviceConfigProxyFake();
mTestableLooper = TestableLooper.get(this);
- mLocationController = spy(new LocationControllerImpl(mContext,
+ mLocationController = new LocationControllerImpl(mContext,
mAppOpsController,
+ mDeviceConfigProxy,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
mock(BroadcastDispatcher.class),
mock(BootCompleteCache.class),
- mUserTracker));
+ mUserTracker);
mTestableLooper.processAllMessages();
}
@@ -86,10 +94,13 @@ public class LocationControllerImplTest extends SysuiTestCase {
mLocationController.addCallback(callback);
mLocationController.addCallback(mock(LocationChangeCallback.class));
- doReturn(false).when(mLocationController).areActiveHighPowerLocationRequests();
+ when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
"", false);
- doReturn(true).when(mLocationController).areActiveHighPowerLocationRequests();
+ when(mAppOpsController.getActiveAppOps())
+ .thenReturn(ImmutableList.of(
+ new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "",
+ System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
"", true);
@@ -135,7 +146,10 @@ public class LocationControllerImplTest extends SysuiTestCase {
verify(callback, times(2)).onLocationSettingsChanged(anyBoolean());
- doReturn(true).when(mLocationController).areActiveHighPowerLocationRequests();
+ when(mAppOpsController.getActiveAppOps())
+ .thenReturn(ImmutableList.of(
+ new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "",
+ System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
"", true);
@@ -145,6 +159,46 @@ public class LocationControllerImplTest extends SysuiTestCase {
}
@Test
+ public void testCallbackNotified_additionalOps() {
+ LocationChangeCallback callback = mock(LocationChangeCallback.class);
+
+ mLocationController.addCallback(callback);
+
+ mTestableLooper.processAllMessages();
+
+ mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION));
+
+ mTestableLooper.processAllMessages();
+
+ verify(callback, times(2)).onLocationSettingsChanged(anyBoolean());
+
+ mDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
+ "true",
+ true);
+ mTestableLooper.processAllMessages();
+
+ when(mAppOpsController.getActiveAppOps())
+ .thenReturn(ImmutableList.of(
+ new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "",
+ System.currentTimeMillis())));
+ mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+ "", true);
+
+ mTestableLooper.processAllMessages();
+
+ verify(callback, times(1)).onLocationActiveChanged(true);
+
+ when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
+ mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+ "", false);
+ mTestableLooper.processAllMessages();
+
+ verify(callback, times(1)).onLocationActiveChanged(false);
+ }
+
+ @Test
public void testCallbackRemoved() {
LocationChangeCallback callback = mock(LocationChangeCallback.class);
diff --git a/services/core/java/com/android/server/app/GameClassifier.java b/services/core/java/com/android/server/app/GameClassifier.java
new file mode 100644
index 000000000000..e20bf46e3b42
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameClassifier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.os.UserHandle;
+
+/**
+ * Responsible for determining if a given application is a game.
+ */
+interface GameClassifier {
+
+ /**
+ * Returns {@code true} if the application associated with the given {@code packageName} is
+ * considered to be a game. The application is queried as the user associated with the given
+ * {@code userHandle}.
+ */
+ boolean isGame(@NonNull String packageName, @NonNull UserHandle userHandle);
+}
diff --git a/services/core/java/com/android/server/app/GameClassifierImpl.java b/services/core/java/com/android/server/app/GameClassifierImpl.java
new file mode 100644
index 000000000000..8f5b0f0a5f42
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameClassifierImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+final class GameClassifierImpl implements GameClassifier {
+
+ private final PackageManager mPackageManager;
+
+ GameClassifierImpl(@NonNull PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ @Override
+ public boolean isGame(@NonNull String packageName, @NonNull UserHandle userHandle) {
+ @ApplicationInfo.Category
+ int applicationCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+
+ try {
+ applicationCategory =
+ mPackageManager.getApplicationInfoAsUser(
+ packageName,
+ 0,
+ userHandle.getIdentifier()).category;
+ } catch (PackageManager.NameNotFoundException ex) {
+ return false;
+ }
+
+ return applicationCategory == ApplicationInfo.CATEGORY_GAME;
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index fc48cd5507a8..0980f4077489 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
@@ -563,7 +564,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
mService.registerPackageReceiver();
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
- mGameServiceController = new GameServiceController(context);
+ mGameServiceController = new GameServiceController(
+ BackgroundThread.getExecutor(),
+ new GameServiceProviderSelectorImpl(
+ getContext().getResources(),
+ getContext().getPackageManager()),
+ new GameServiceProviderInstanceFactoryImpl(getContext()));
}
}
@@ -586,6 +592,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
@Override
+ public void onUserUnlocking(@NonNull TargetUser user) {
+ super.onUserUnlocking(user);
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyUserUnlocking(user);
+ }
+ }
+
+ @Override
public void onUserStopping(@NonNull TargetUser user) {
mService.onUserStopping(user.getUserIdentifier());
if (mGameServiceController != null) {
diff --git a/services/core/java/com/android/server/app/GameServiceConnection.java b/services/core/java/com/android/server/app/GameServiceConnection.java
deleted file mode 100644
index b60778915de7..000000000000
--- a/services/core/java/com/android/server/app/GameServiceConnection.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.app;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.games.GameService;
-import android.service.games.IGameService;
-import android.util.Slog;
-
-final class GameServiceConnection {
- private static final String TAG = "GameServiceConnection";
- private static final boolean DEBUG = false;
-
- private final Context mContext;
- private final ComponentName mGameServiceComponent;
- private final int mUser;
- private boolean mIsBound;
- @Nullable
- private IGameService mGameService;
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) {
- Slog.d(TAG, "onServiceConnected to " + name + " for user(" + mUser + ")");
- }
-
- mGameService = IGameService.Stub.asInterface(service);
- try {
- mGameService.connected();
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException while calling ready", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) {
- Slog.d(TAG, "onServiceDisconnected to " + name);
- }
-
- mGameService = null;
- }
- };
-
- GameServiceConnection(Context context, ComponentName gameServiceComponent, int user) {
- mContext = context;
- mGameServiceComponent = gameServiceComponent;
- mUser = user;
- }
-
- public void connect() {
- if (mIsBound) {
- Slog.v(TAG, "Already bound, ignoring start.");
- return;
- }
-
- Intent intent = new Intent(GameService.SERVICE_INTERFACE);
- intent.setComponent(mGameServiceComponent);
- mIsBound = mContext.bindServiceAsUser(intent, mConnection,
- Context.BIND_AUTO_CREATE
- | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, new UserHandle(mUser));
- if (!mIsBound) {
- Slog.w(TAG, "Failed binding to game service " + mGameServiceComponent);
- }
- }
-
- public void disconnect() {
- try {
- if (mGameService != null) {
- mGameService.disconnected();
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in shutdown", e);
- }
-
- if (mIsBound) {
- mContext.unbindService(mConnection);
- mIsBound = false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java
index d056ea9f359a..ac720b9c2971 100644
--- a/services/core/java/com/android/server/app/GameServiceController.java
+++ b/services/core/java/com/android/server/app/GameServiceController.java
@@ -18,40 +18,56 @@ package com.android.server.app;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.service.games.GameService;
-import android.text.TextUtils;
+import android.annotation.WorkerThread;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
-import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+/**
+ * Responsible for managing the Game Service API.
+ *
+ * Key responsibilities selecting the active Game Service provider, binding to the Game Service
+ * provider services, and driving the GameService/GameSession lifecycles.
+ */
final class GameServiceController {
private static final String TAG = "GameServiceController";
- private static final boolean DEBUG = false;
- private final Context mContext;
- @Nullable
- private SystemService.TargetUser mCurrentForegroundUser;
- private boolean mHasBootCompleted;
- @Nullable
- private GameServiceConnection mGameServiceConnection;
+ private final Object mLock = new Object();
+ private final Executor mBackgroundExecutor;
+ private final GameServiceProviderSelector mGameServiceProviderSelector;
+ private final GameServiceProviderInstanceFactory mGameServiceProviderInstanceFactory;
- GameServiceController(Context context) {
- mContext = context;
+ private volatile boolean mHasBootCompleted;
+ @Nullable
+ private volatile SystemService.TargetUser mCurrentForegroundUser;
+ @GuardedBy("mLock")
+ @Nullable
+ private volatile GameServiceProviderConfiguration mActiveGameServiceProviderConfiguration;
+ @GuardedBy("mLock")
+ @Nullable
+ private volatile GameServiceProviderInstance mGameServiceProviderInstance;
+
+ GameServiceController(
+ @NonNull Executor backgroundExecutor,
+ @NonNull GameServiceProviderSelector gameServiceProviderSelector,
+ @NonNull GameServiceProviderInstanceFactory gameServiceProviderInstanceFactory) {
+ mGameServiceProviderInstanceFactory = gameServiceProviderInstanceFactory;
+ mBackgroundExecutor = backgroundExecutor;
+ mGameServiceProviderSelector = gameServiceProviderSelector;
}
void onBootComplete() {
+ if (mHasBootCompleted) {
+ return;
+ }
mHasBootCompleted = true;
- evaluateGameServiceConnection();
+ mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider);
}
void notifyUserStarted(@NonNull SystemService.TargetUser user) {
@@ -59,96 +75,86 @@ final class GameServiceController {
return;
}
- mCurrentForegroundUser = user;
- evaluateGameServiceConnection();
+ setCurrentForegroundUserAndEvaluateProvider(user);
}
void notifyNewForegroundUser(@NonNull SystemService.TargetUser user) {
- mCurrentForegroundUser = user;
- evaluateGameServiceConnection();
+ setCurrentForegroundUserAndEvaluateProvider(user);
}
- void notifyUserStopped(@NonNull SystemService.TargetUser user) {
- if (mCurrentForegroundUser == null
- || mCurrentForegroundUser.getUserIdentifier() != user.getUserIdentifier()) {
+ void notifyUserUnlocking(@NonNull SystemService.TargetUser user) {
+ boolean isSameAsForegroundUser =
+ mCurrentForegroundUser != null
+ && mCurrentForegroundUser.getUserIdentifier() == user.getUserIdentifier();
+ if (!isSameAsForegroundUser) {
return;
}
- mCurrentForegroundUser = null;
- evaluateGameServiceConnection();
+ // It is likely that the Game Service provider's components are not Direct Boot mode aware
+ // and will not be capable of running until the user has unlocked the device. To allow for
+ // this we re-evaluate the active game service provider once these components are available.
+
+ mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider);
}
- private void evaluateGameServiceConnection() {
- if (!mHasBootCompleted) {
+ void notifyUserStopped(@NonNull SystemService.TargetUser user) {
+ boolean isSameAsForegroundUser =
+ mCurrentForegroundUser != null
+ && mCurrentForegroundUser.getUserIdentifier() == user.getUserIdentifier();
+ if (!isSameAsForegroundUser) {
return;
}
- // TODO(b/204565942): Only shutdown the existing service connection if the game service
- // provider or user has changed.
- if (mGameServiceConnection != null) {
- mGameServiceConnection.disconnect();
- mGameServiceConnection = null;
- }
+ setCurrentForegroundUserAndEvaluateProvider(null);
+ }
- boolean isUserSupported =
- mCurrentForegroundUser != null
- && mCurrentForegroundUser.isFull()
- && !mCurrentForegroundUser.isManagedProfile();
- if (!isUserSupported) {
- if (DEBUG && mCurrentForegroundUser != null) {
- Slog.d(TAG, "User not supported: " + mCurrentForegroundUser);
- }
+ private void setCurrentForegroundUserAndEvaluateProvider(
+ @Nullable SystemService.TargetUser user) {
+ boolean hasUserChanged =
+ !Objects.equals(mCurrentForegroundUser, user);
+ if (!hasUserChanged) {
return;
}
+ mCurrentForegroundUser = user;
+
+ mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider);
+ }
- ComponentName gameServiceComponentName =
- determineGameServiceComponentName(mCurrentForegroundUser.getUserIdentifier());
- if (gameServiceComponentName == null) {
+ @WorkerThread
+ private void evaluateActiveGameServiceProvider() {
+ if (!mHasBootCompleted) {
return;
}
- mGameServiceConnection = new GameServiceConnection(
- mContext,
- gameServiceComponentName,
- mCurrentForegroundUser.getUserIdentifier());
- mGameServiceConnection.connect();
- }
+ synchronized (mLock) {
+ GameServiceProviderConfiguration selectedGameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(mCurrentForegroundUser);
- @Nullable
- private ComponentName determineGameServiceComponentName(int userId) {
- String gameServicePackage =
- mContext.getResources().getString(
- com.android.internal.R.string.config_systemGameService);
- if (TextUtils.isEmpty(gameServicePackage)) {
- if (DEBUG) {
- Slog.d(TAG, "No game service package defined");
+ boolean didActiveGameServiceProviderChanged =
+ !Objects.equals(selectedGameServiceProviderConfiguration,
+ mActiveGameServiceProviderConfiguration);
+ if (!didActiveGameServiceProviderChanged) {
+ return;
}
- return null;
- }
- List<ResolveInfo> gameServiceResolveInfos =
- mContext.getPackageManager().queryIntentServicesAsUser(
- new Intent(GameService.SERVICE_INTERFACE).setPackage(gameServicePackage),
- PackageManager.MATCH_SYSTEM_ONLY,
- userId);
+ if (mGameServiceProviderInstance != null) {
+ Slog.i(TAG, "Stopping Game Service provider: "
+ + mActiveGameServiceProviderConfiguration);
+ mGameServiceProviderInstance.stop();
+ }
- if (gameServiceResolveInfos.isEmpty()) {
- Slog.v(TAG, "No available game service found for user id: " + userId);
- return null;
- }
+ mActiveGameServiceProviderConfiguration = selectedGameServiceProviderConfiguration;
- for (ResolveInfo resolveInfo : gameServiceResolveInfos) {
- if (resolveInfo.serviceInfo == null) {
- continue;
- }
- final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
- if (!serviceInfo.isEnabled()) {
- continue;
+ if (mActiveGameServiceProviderConfiguration == null) {
+ return;
}
- return serviceInfo.getComponentName();
- }
- Slog.v(TAG, "No game service found for user id: " + userId);
- return null;
+ Slog.i(TAG,
+ "Starting Game Service provider: " + mActiveGameServiceProviderConfiguration);
+ mGameServiceProviderInstance =
+ mGameServiceProviderInstanceFactory.create(
+ mActiveGameServiceProviderConfiguration);
+ mGameServiceProviderInstance.start();
+ }
}
}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java
new file mode 100644
index 000000000000..7c8f251f35fe
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import java.util.Objects;
+
+/**
+ * Representation of a {@link android.service.games.GameService} provider configuration.
+ */
+final class GameServiceProviderConfiguration {
+ private final UserHandle mUserHandle;
+ private final ComponentName mGameServiceComponentName;
+ private final ComponentName mGameSessionServiceComponentName;
+
+ GameServiceProviderConfiguration(
+ @NonNull UserHandle userHandle,
+ @NonNull ComponentName gameServiceComponentName,
+ @NonNull ComponentName gameSessionServiceComponentName) {
+ Objects.requireNonNull(userHandle);
+ Objects.requireNonNull(gameServiceComponentName);
+ Objects.requireNonNull(gameSessionServiceComponentName);
+
+ this.mUserHandle = userHandle;
+ this.mGameServiceComponentName = gameServiceComponentName;
+ this.mGameSessionServiceComponentName = gameSessionServiceComponentName;
+ }
+
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ @NonNull
+ public ComponentName getGameServiceComponentName() {
+ return mGameServiceComponentName;
+ }
+
+ @NonNull
+ public ComponentName getGameSessionServiceComponentName() {
+ return mGameSessionServiceComponentName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GameServiceProviderConfiguration)) {
+ return false;
+ }
+
+ GameServiceProviderConfiguration that = (GameServiceProviderConfiguration) o;
+ return mUserHandle.equals(that.mUserHandle)
+ && mGameServiceComponentName.equals(that.mGameServiceComponentName)
+ && mGameSessionServiceComponentName.equals(that.mGameSessionServiceComponentName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserHandle, mGameServiceComponentName,
+ mGameSessionServiceComponentName);
+ }
+
+ @Override
+ public String toString() {
+ return "GameServiceProviderConfiguration{"
+ + "mUserHandle="
+ + mUserHandle
+ + ", gameServiceComponentName="
+ + mGameServiceComponentName
+ + ", gameSessionServiceComponentName="
+ + mGameSessionServiceComponentName
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstance.java b/services/core/java/com/android/server/app/GameServiceProviderInstance.java
new file mode 100644
index 000000000000..e83f9acca66e
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstance.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+/**
+ * Representation of an instance of a Game Service provider.
+ *
+ * This includes maintaining the bindings and driving the interactions with the provider's
+ * implementations of {@link android.service.games.GameService} and
+ * {@link android.service.games.GameSessionService}.
+ */
+interface GameServiceProviderInstance {
+ /**
+ * Begins running the Game Service provider instance.
+ */
+ void start();
+
+ /**
+ * Stops running the Game Service provider instance.
+ */
+ void stop();
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java
new file mode 100644
index 000000000000..7640cc555446
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+
+/**
+ * Factory for creating {@link GameServiceProviderInstance}.
+ */
+interface GameServiceProviderInstanceFactory {
+
+ @NonNull
+ GameServiceProviderInstance create(@NonNull
+ GameServiceProviderConfiguration gameServiceProviderConfiguration);
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
new file mode 100644
index 000000000000..d5ac03ab7c0d
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.content.Intent;
+import android.service.games.GameService;
+import android.service.games.GameSessionService;
+import android.service.games.IGameService;
+import android.service.games.IGameSessionService;
+
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.BackgroundThread;
+
+final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory {
+ private final Context mContext;
+
+ GameServiceProviderInstanceFactoryImpl(@NonNull Context context) {
+ this.mContext = context;
+ }
+
+ @NonNull
+ @Override
+ public GameServiceProviderInstance create(@NonNull
+ GameServiceProviderConfiguration gameServiceProviderConfiguration) {
+ return new GameServiceProviderInstanceImpl(
+ gameServiceProviderConfiguration.getUserHandle(),
+ BackgroundThread.getExecutor(),
+ new GameClassifierImpl(mContext.getPackageManager()),
+ ActivityTaskManager.getService(),
+ new GameServiceConnector(mContext, gameServiceProviderConfiguration),
+ new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration));
+ }
+
+ private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> {
+ private static final int DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT = 0;
+ private static final int BINDING_FLAGS = Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
+
+ GameServiceConnector(
+ @NonNull Context context,
+ @NonNull GameServiceProviderConfiguration configuration) {
+ super(context, new Intent(GameService.ACTION_GAME_SERVICE)
+ .setComponent(configuration.getGameServiceComponentName()),
+ BINDING_FLAGS, configuration.getUserHandle().getIdentifier(),
+ IGameService.Stub::asInterface);
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT;
+ }
+ }
+
+ private static final class GameSessionServiceConnector extends
+ ServiceConnector.Impl<IGameSessionService> {
+ private static final int DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT = 0;
+ private static final int BINDING_FLAGS =
+ Context.BIND_TREAT_LIKE_ACTIVITY
+ | Context.BIND_SCHEDULE_LIKE_TOP_APP
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
+
+ GameSessionServiceConnector(
+ @NonNull Context context,
+ @NonNull GameServiceProviderConfiguration configuration) {
+ super(context, new Intent(GameSessionService.ACTION_GAME_SESSION_SERVICE)
+ .setComponent(configuration.getGameSessionServiceComponentName()),
+ BINDING_FLAGS, configuration.getUserHandle().getIdentifier(),
+ IGameSessionService.Stub::asInterface);
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
new file mode 100644
index 000000000000..3f3f257aedc5
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.games.CreateGameSessionRequest;
+import android.service.games.IGameService;
+import android.service.games.IGameSession;
+import android.service.games.IGameSessionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+final class GameServiceProviderInstanceImpl implements GameServiceProviderInstance {
+ private static final String TAG = "GameServiceProviderInstance";
+ private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
+ private static final boolean DEBUG = false;
+
+ private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
+ if (componentName == null) {
+ return;
+ }
+
+ mBackgroundExecutor.execute(() -> {
+ GameServiceProviderInstanceImpl.this.onTaskCreated(taskId, componentName);
+ });
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) throws RemoteException {
+ mBackgroundExecutor.execute(() -> {
+ GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId);
+ });
+ }
+ };
+ private final Object mLock = new Object();
+ private final UserHandle mUserHandle;
+ private final Executor mBackgroundExecutor;
+ private final GameClassifier mGameClassifier;
+ private final IActivityTaskManager mActivityTaskManager;
+ private final ServiceConnector<IGameService> mGameServiceConnector;
+ private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector;
+
+ @GuardedBy("mLock")
+ private final ConcurrentHashMap<Integer, GameSessionRecord> mGameSessions =
+ new ConcurrentHashMap<>();
+ @GuardedBy("mLock")
+ private volatile boolean mIsRunning;
+
+ GameServiceProviderInstanceImpl(
+ UserHandle userHandle,
+ @NonNull Executor backgroundExecutor,
+ @NonNull GameClassifier gameClassifier,
+ @NonNull IActivityTaskManager activityTaskManager,
+ @NonNull ServiceConnector<IGameService> gameServiceConnector,
+ @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) {
+ mUserHandle = userHandle;
+ mBackgroundExecutor = backgroundExecutor;
+ mGameClassifier = gameClassifier;
+ mActivityTaskManager = activityTaskManager;
+ mGameServiceConnector = gameServiceConnector;
+ mGameSessionServiceConnector = gameSessionServiceConnector;
+ }
+
+ @Override
+ public void start() {
+ synchronized (mLock) {
+ startLocked();
+ }
+ }
+
+ @Override
+ public void stop() {
+ synchronized (mLock) {
+ stopLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void startLocked() {
+ if (mIsRunning) {
+ return;
+ }
+ mIsRunning = true;
+
+ // TODO(b/204503192): In cases where the connection to the game service fails retry with
+ // back off mechanism.
+ AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> {
+ gameService.connected();
+ });
+
+ try {
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to register task stack listener", e);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void stopLocked() {
+ if (!mIsRunning) {
+ return;
+ }
+ mIsRunning = false;
+
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to unregister task stack listener", e);
+ }
+
+ for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+ IGameSession gameSession = gameSessionRecord.getGameSession();
+ if (gameSession == null) {
+ continue;
+ }
+
+ try {
+ gameSession.destroy();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
+ }
+ }
+ mGameSessions.clear();
+
+ // TODO(b/204503192): It is possible that the game service is disconnected. In this
+ // case we should avoid rebinding just to shut it down again.
+ AndroidFuture<Void> unusedPostDisconnectedFuture =
+ mGameServiceConnector.post(gameService -> {
+ gameService.disconnected();
+ });
+ mGameServiceConnector.unbind();
+ mGameSessionServiceConnector.unbind();
+ }
+
+ private void onTaskCreated(int taskId, @NonNull ComponentName componentName) {
+ String packageName = componentName.getPackageName();
+ if (!mGameClassifier.isGame(packageName, mUserHandle)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ createGameSessionLocked(taskId, componentName);
+ }
+ }
+
+ private void onTaskRemoved(int taskId) {
+ synchronized (mLock) {
+ boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
+ if (!isTaskAssociatedWithGameSession) {
+ return;
+ }
+
+ destroyGameSessionLocked(taskId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void createGameSessionLocked(int sessionId, @NonNull ComponentName componentName) {
+ if (DEBUG) {
+ Slog.i(TAG, "createGameSession() id: " + sessionId + " component: " + componentName);
+ }
+
+ if (!mIsRunning) {
+ return;
+ }
+
+ GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId);
+ if (existingGameSessionRecord != null) {
+ Slog.w(TAG, "Existing game session found for task (id: " + sessionId
+ + ") creation. Ignoring.");
+ return;
+ }
+
+ GameSessionRecord gameSessionRecord = GameSessionRecord.pendingGameSession(sessionId,
+ componentName);
+ mGameSessions.put(sessionId, gameSessionRecord);
+
+ // TODO(b/207035150): Allow the game service provider to determine if a game session
+ // should be created. For now we will assume all games should have a session.
+ AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>()
+ .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ .whenCompleteAsync((gameSessionIBinder, exception) -> {
+ IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder);
+ if (exception != null || gameSession == null) {
+ Slog.w(TAG, "Failed to create GameSession: " + gameSessionRecord,
+ exception);
+ synchronized (mLock) {
+ destroyGameSessionLocked(sessionId);
+ }
+ return;
+ }
+
+ synchronized (mLock) {
+ attachGameSessionLocked(sessionId, gameSession);
+ }
+ }, mBackgroundExecutor);
+
+ AndroidFuture<Void> unusedPostCreateGameSessionFuture =
+ mGameSessionServiceConnector.post(gameService -> {
+ CreateGameSessionRequest createGameSessionRequest =
+ new CreateGameSessionRequest(sessionId, componentName.getPackageName());
+ gameService.create(createGameSessionRequest, gameSessionFuture);
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) {
+ if (DEBUG) {
+ Slog.i(TAG, "attachGameSession() id: " + sessionId);
+ }
+
+ GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId);
+ if (gameSessionRecord == null) {
+ Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId);
+
+ try {
+ gameSession.destroy();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
+ }
+ return;
+ }
+
+ mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession));
+ }
+
+ @GuardedBy("mLock")
+ private void destroyGameSessionLocked(int sessionId) {
+ // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider
+ // to only when the associated task is running. Right now it is possible for a task to
+ // move into the background and for all associated processes to die and for the Game Session
+ // provider's GameSessionService to continue to be running. Ideally we could unbind the
+ // service when this happens.
+ if (DEBUG) {
+ Slog.i(TAG, "destroyGameSession() id: " + sessionId);
+ }
+
+ GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId);
+ if (gameSessionRecord == null) {
+ if (DEBUG) {
+ Slog.w(TAG, "No game session found for id: " + sessionId);
+ }
+ return;
+ }
+
+ IGameSession gameSession = gameSessionRecord.getGameSession();
+ if (gameSession != null) {
+ try {
+ gameSession.destroy();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
+ }
+ }
+
+ if (mGameSessions.isEmpty()) {
+ if (DEBUG) {
+ Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService");
+ }
+
+ if (mGameSessionServiceConnector != null) {
+ mGameSessionServiceConnector.unbind();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelector.java b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
new file mode 100644
index 000000000000..51d35157b332
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.Nullable;
+
+import com.android.server.SystemService;
+
+/**
+ * Responsible for determining what the active Game Service provider should be.
+ */
+interface GameServiceProviderSelector {
+
+ /**
+ * Returns the {@link GameServiceProviderConfiguration} associated with the selected Game
+ * Service provider for the given user or {@code null} if none should be used.
+ */
+ @Nullable
+ GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user);
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
new file mode 100644
index 000000000000..54ef70715b86
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.UserHandle;
+import android.service.games.GameService;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.server.SystemService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+final class GameServiceProviderSelectorImpl implements GameServiceProviderSelector {
+ private static final String TAG = "GameServiceProviderSelector";
+ private static final String GAME_SERVICE_NODE_NAME = "game-service";
+ private static final boolean DEBUG = false;
+
+ private final Resources mResources;
+ private final PackageManager mPackageManager;
+
+ GameServiceProviderSelectorImpl(@NonNull Resources resources,
+ @NonNull PackageManager packageManager) {
+ mResources = resources;
+ mPackageManager = packageManager;
+ }
+
+ @Override
+ @Nullable
+ public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user) {
+ if (user == null) {
+ return null;
+ }
+
+ boolean isUserSupported = user.isFull() && !user.isManagedProfile();
+ if (!isUserSupported) {
+ Slog.i(TAG, "Game Service not supported for user: " + user.getUserIdentifier());
+ return null;
+ }
+
+ String gameServicePackage =
+ mResources.getString(
+ com.android.internal.R.string.config_systemGameService);
+
+ if (TextUtils.isEmpty(gameServicePackage)) {
+ Slog.w(TAG, "No game service package defined");
+ return null;
+ }
+
+ int userId = user.getUserIdentifier();
+ List<ResolveInfo> gameServiceResolveInfos =
+ mPackageManager.queryIntentServicesAsUser(
+ new Intent(GameService.ACTION_GAME_SERVICE).setPackage(gameServicePackage),
+ PackageManager.GET_META_DATA | PackageManager.MATCH_SYSTEM_ONLY,
+ userId);
+ if (DEBUG) {
+ Slog.i(TAG, "Querying package: " + gameServicePackage + " and user id: " + userId);
+ Slog.i(TAG, "Found resolve infos: " + gameServiceResolveInfos);
+ }
+
+ if (gameServiceResolveInfos == null || gameServiceResolveInfos.isEmpty()) {
+ Slog.w(TAG, "No available game service found for user id: " + userId);
+ return null;
+ }
+
+ GameServiceProviderConfiguration selectedProvider = null;
+ for (ResolveInfo resolveInfo : gameServiceResolveInfos) {
+ if (resolveInfo.serviceInfo == null) {
+ continue;
+ }
+ ServiceInfo gameServiceServiceInfo = resolveInfo.serviceInfo;
+
+ ComponentName gameSessionServiceComponentName =
+ determineGameSessionServiceFromGameService(gameServiceServiceInfo);
+ if (gameSessionServiceComponentName == null) {
+ continue;
+ }
+
+ selectedProvider =
+ new GameServiceProviderConfiguration(
+ new UserHandle(userId),
+ gameServiceServiceInfo.getComponentName(),
+ gameSessionServiceComponentName);
+ break;
+ }
+
+ if (selectedProvider == null) {
+ Slog.w(TAG, "No valid game service found for user id: " + userId);
+ return null;
+ }
+
+ return selectedProvider;
+ }
+
+ @Nullable
+ private ComponentName determineGameSessionServiceFromGameService(
+ @NonNull ServiceInfo gameServiceServiceInfo) {
+ String gameSessionService;
+ try (XmlResourceParser parser = gameServiceServiceInfo.loadXmlMetaData(mPackageManager,
+ GameService.SERVICE_META_DATA)) {
+ if (parser == null) {
+ Slog.w(TAG, "No " + GameService.SERVICE_META_DATA + " meta-data found for "
+ + gameServiceServiceInfo.getComponentName());
+ return null;
+ }
+
+ Resources resources = mPackageManager.getResourcesForApplication(
+ gameServiceServiceInfo.packageName);
+
+ AttributeSet attributeSet = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Do nothing
+ }
+
+ boolean isStartingTagGameService = GAME_SERVICE_NODE_NAME.equals(parser.getName());
+ if (!isStartingTagGameService) {
+ Slog.w(TAG, "Meta-data does not start with " + GAME_SERVICE_NODE_NAME + " tag");
+ return null;
+ }
+
+ TypedArray array = resources.obtainAttributes(attributeSet,
+ com.android.internal.R.styleable.GameService);
+ gameSessionService = array.getString(
+ com.android.internal.R.styleable.GameService_gameSessionService);
+ array.recycle();
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ex) {
+ Slog.w("Error while parsing meta-data for " + gameServiceServiceInfo.getComponentName(),
+ ex);
+ return null;
+ }
+
+ if (TextUtils.isEmpty(gameSessionService)) {
+ Slog.w(TAG, "No gameSessionService specified");
+ return null;
+ }
+ ComponentName componentName =
+ new ComponentName(gameServiceServiceInfo.packageName, gameSessionService);
+
+ try {
+ mPackageManager.getServiceInfo(componentName, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException ex) {
+ Slog.w(TAG, "GameSessionService does not exist: " + componentName);
+ return null;
+ }
+
+ return componentName;
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java
new file mode 100644
index 000000000000..329e9e8144e0
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameSessionRecord.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.service.games.IGameSession;
+
+import java.util.Objects;
+
+final class GameSessionRecord {
+
+ private final int mTaskId;
+ private final ComponentName mRootComponentName;
+ @Nullable
+ private final IGameSession mIGameSession;
+
+ static GameSessionRecord pendingGameSession(int taskId, ComponentName rootComponentName) {
+ return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null);
+ }
+
+ private GameSessionRecord(
+ int taskId,
+ @NonNull ComponentName rootComponentName,
+ @Nullable IGameSession gameSession) {
+ this.mTaskId = taskId;
+ this.mRootComponentName = rootComponentName;
+ this.mIGameSession = gameSession;
+ }
+
+ @NonNull
+ public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) {
+ Objects.requireNonNull(gameSession);
+ return new GameSessionRecord(mTaskId, mRootComponentName, gameSession);
+ }
+
+ @Nullable
+ public IGameSession getGameSession() {
+ return mIGameSession;
+ }
+
+ @Override
+ public String toString() {
+ return "GameSessionRecord{"
+ + "mTaskId="
+ + mTaskId
+ + ", mRootComponentName="
+ + mRootComponentName
+ + ", mIGameSession="
+ + mIGameSession
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof GameSessionRecord)) {
+ return false;
+ }
+
+ GameSessionRecord that = (GameSessionRecord) o;
+ return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName)
+ && Objects.equals(mIGameSession, that.mIGameSession);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId, mRootComponentName, mIGameSession);
+ }
+}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b333ed24eb1d..406b2dd2486d 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -817,7 +817,7 @@ public final class PlaybackActivityMonitor
// the same time if we still have a public client.
while (clientIterator.hasNext()) {
PlayMonitorClient pmc = clientIterator.next();
- if (pcdb.equals(pmc.mDispatcherCb)) {
+ if (pcdb.asBinder().equals(pmc.mDispatcherCb.asBinder())) {
pmc.release();
clientIterator.remove();
} else {
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index e2e56ae8e620..9dd7daf1a1cc 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -36,6 +36,8 @@ import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.OverrideAllowedState;
@@ -220,15 +222,47 @@ final class CompatConfig {
}
/**
- * Overrides the enabled state for a given change and app.
+ * Adds compat config overrides for multiple packages.
+ *
+ * <p>Equivalent to calling
+ * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} on each entry
+ * in {@code overridesByPackage}, but the state of the compat config will be updated only
+ * once instead of for each package.
+ *
+ * @param overridesByPackage map from package name to compat config overrides to add for that
+ * package.
+ * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overridesByPackage}.
+ */
+ synchronized void addAllPackageOverrides(
+ CompatibilityOverridesByPackageConfig overridesByPackage,
+ boolean skipUnknownChangeIds) {
+ for (String packageName : overridesByPackage.packageNameToOverrides.keySet()) {
+ addPackageOverridesWithoutSaving(
+ overridesByPackage.packageNameToOverrides.get(packageName), packageName,
+ skipUnknownChangeIds);
+ }
+ saveOverrides();
+ invalidateCache();
+ }
+
+ /**
+ * Adds compat config overrides for a given package.
*
+ * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
*
- * @param overrides list of overrides to default changes config.
- * @param packageName app for which the overrides will be applied.
+ * @param overrides list of compat config overrides to add for the given package.
+ * @param packageName app for which the overrides will be applied.
* @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overrides}.
*/
synchronized void addPackageOverrides(CompatibilityOverrideConfig overrides,
String packageName, boolean skipUnknownChangeIds) {
+ addPackageOverridesWithoutSaving(overrides, packageName, skipUnknownChangeIds);
+ saveOverrides();
+ invalidateCache();
+ }
+
+ private void addPackageOverridesWithoutSaving(CompatibilityOverrideConfig overrides,
+ String packageName, boolean skipUnknownChangeIds) {
for (Long changeId : overrides.overrides.keySet()) {
if (skipUnknownChangeIds && !isKnownChangeId(changeId)) {
Slog.w(TAG, "Trying to add overrides for unknown Change ID " + changeId + ". "
@@ -237,8 +271,6 @@ final class CompatConfig {
}
addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
}
- saveOverrides();
- invalidateCache();
}
private boolean addOverrideUnsafe(long changeId, String packageName,
@@ -344,6 +376,36 @@ final class CompatConfig {
}
/**
+ * Removes overrides with a specified change ID that were previously added via
+ * {@link #addOverride(long, String, boolean)} or
+ * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for multiple
+ * packages.
+ *
+ * <p>Equivalent to calling
+ * {@link #removePackageOverrides(CompatibilityOverridesToRemoveConfig, String)} on each entry
+ * in {@code overridesToRemoveByPackage}, but the state of the compat config will be updated
+ * only once instead of for each package.
+ *
+ * @param overridesToRemoveByPackage map from package name to a list of change IDs for
+ * which to restore the default behaviour for that
+ * package.
+ */
+ synchronized void removeAllPackageOverrides(
+ CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
+ boolean shouldInvalidateCache = false;
+ for (String packageName :
+ overridesToRemoveByPackage.packageNameToOverridesToRemove.keySet()) {
+ shouldInvalidateCache |= removePackageOverridesWithoutSaving(
+ overridesToRemoveByPackage.packageNameToOverridesToRemove.get(packageName),
+ packageName);
+ }
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
+ }
+ }
+
+ /**
* Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
* {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain
* package.
@@ -377,6 +439,16 @@ final class CompatConfig {
*/
synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
String packageName) {
+ boolean shouldInvalidateCache = removePackageOverridesWithoutSaving(overridesToRemove,
+ packageName);
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
+ }
+ }
+
+ private boolean removePackageOverridesWithoutSaving(
+ CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) {
boolean shouldInvalidateCache = false;
for (Long changeId : overridesToRemove.changeIds) {
if (!isKnownChangeId(changeId)) {
@@ -386,10 +458,7 @@ final class CompatConfig {
}
shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName);
}
- if (shouldInvalidateCache) {
- saveOverrides();
- invalidateCache();
- }
+ return shouldInvalidateCache;
}
private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6ea89d445402..aab6281e6cd1 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -47,6 +47,8 @@ import com.android.internal.compat.ChangeReporter;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.IPlatformCompat;
@@ -226,9 +228,19 @@ public class PlatformCompat extends IPlatformCompat.Stub {
}
@Override
+ public void putAllOverridesOnReleaseBuilds(
+ CompatibilityOverridesByPackageConfig overridesByPackage) {
+ checkCompatChangeOverrideOverridablePermission();
+ for (CompatibilityOverrideConfig overrides :
+ overridesByPackage.packageNameToOverrides.values()) {
+ checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
+ }
+ mCompatConfig.addAllPackageOverrides(overridesByPackage, /* skipUnknownChangeIds= */ true);
+ }
+
+ @Override
public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
String packageName) {
- // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
checkCompatChangeOverrideOverridablePermission();
checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
@@ -280,10 +292,20 @@ public class PlatformCompat extends IPlatformCompat.Stub {
}
@Override
+ public void removeAllOverridesOnReleaseBuilds(
+ CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
+ checkCompatChangeOverrideOverridablePermission();
+ for (CompatibilityOverridesToRemoveConfig overridesToRemove :
+ overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) {
+ checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
+ }
+ mCompatConfig.removeAllPackageOverrides(overridesToRemoveByPackage);
+ }
+
+ @Override
public void removeOverridesOnReleaseBuilds(
CompatibilityOverridesToRemoveConfig overridesToRemove,
String packageName) {
- // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
checkCompatChangeOverrideOverridablePermission();
checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bf4ef4879c9a..a2fed291cb38 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.Manifest.permission.CONTROL_VPN;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -932,6 +934,7 @@ public class Vpn {
* - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn().
* - oldPackage null, newPackage=LEGACY_VPN: Used internally to disconnect
* and revoke any current app VPN and re-prepare legacy vpn.
+ * - oldPackage null, newPackage null: always returns true for backward compatibility.
*
* TODO: Rename the variables - or split this method into two - and end this confusion.
* TODO: b/29032008 Migrate code from prepare(oldPackage=non-null, newPackage=LEGACY_VPN)
@@ -945,6 +948,18 @@ public class Vpn {
*/
public synchronized boolean prepare(
String oldPackage, String newPackage, @VpnManager.VpnType int vpnType) {
+ // Except for Settings and VpnDialogs, the caller should be matched one of oldPackage or
+ // newPackage. Otherwise, non VPN owner might get the VPN always-on status of the VPN owner.
+ // See b/191382886.
+ if (mContext.checkCallingOrSelfPermission(CONTROL_VPN) != PERMISSION_GRANTED) {
+ if (oldPackage != null) {
+ verifyCallingUidAndPackage(oldPackage);
+ }
+ if (newPackage != null) {
+ verifyCallingUidAndPackage(newPackage);
+ }
+ }
+
if (oldPackage != null) {
// Stop an existing always-on VPN from being dethroned by other apps.
if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) {
@@ -1859,14 +1874,13 @@ public class Vpn {
}
private void enforceControlPermission() {
- mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller");
+ mContext.enforceCallingPermission(CONTROL_VPN, "Unauthorized Caller");
}
private void enforceControlPermissionOrInternalCaller() {
// Require the caller to be either an application with CONTROL_VPN permission or a process
// in the system server.
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN,
- "Unauthorized Caller");
+ mContext.enforceCallingOrSelfPermission(CONTROL_VPN, "Unauthorized Caller");
}
private void enforceSettingsPermission() {
@@ -3176,8 +3190,9 @@ public class Vpn {
}
private void verifyCallingUidAndPackage(String packageName) {
- if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) {
- throw new SecurityException("Mismatched package and UID");
+ final int callingUid = Binder.getCallingUid();
+ if (getAppUid(packageName, mUserId) != callingUid) {
+ throw new SecurityException(packageName + " does not belong to uid " + callingUid);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 3d04037185f3..261aa32f093e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -347,6 +347,7 @@ public class InputManagerService extends IInputManager.Stub
private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType,
int samplingPeriodUs, int maxBatchReportLatencyUs);
private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType);
+ private static native void nativeCancelCurrentTouch(long ptr);
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -2510,6 +2511,16 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ public void cancelCurrentTouch() {
+ if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+ "cancelCurrentTouch()")) {
+ throw new SecurityException("Requires MONITOR_INPUT permission");
+ }
+
+ nativeCancelCurrentTouch(mPtr);
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 9078f3ffeb54..cc5aaf4f7f45 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -41,8 +41,10 @@ import android.util.Pair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -320,9 +322,8 @@ public abstract class IContextHubWrapper {
private static class ContextHubWrapperAidl extends IContextHubWrapper {
private android.hardware.contexthub.IContextHub mHub;
- private ICallback mCallback = null;
-
- private ContextHubAidlCallback mAidlCallback = new ContextHubAidlCallback();
+ private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
+ new HashMap<>();
// Use this thread in case where the execution requires to be on a service thread.
// For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
@@ -332,6 +333,14 @@ public abstract class IContextHubWrapper {
private class ContextHubAidlCallback extends
android.hardware.contexthub.IContextHubCallback.Stub {
+ private final int mContextHubId;
+ private final ICallback mCallback;
+
+ ContextHubAidlCallback(int contextHubId, ICallback callback) {
+ mContextHubId = contextHubId;
+ mCallback = callback;
+ }
+
public void handleNanoappInfo(android.hardware.contexthub.NanoappInfo[] appInfo) {
List<NanoAppState> nanoAppStateList =
ContextHubServiceUtil.createNanoAppStateList(appInfo);
@@ -481,8 +490,8 @@ public abstract class IContextHubWrapper {
}
public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
- mCallback = callback;
- mHub.registerCallback(contextHubId, mAidlCallback);
+ mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+ mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
}
@ContextHubTransaction.Result
@@ -508,10 +517,18 @@ public abstract class IContextHubWrapper {
protected ICallback mCallback = null;
- protected final ContextHubWrapperHidlCallback mHidlCallback =
- new ContextHubWrapperHidlCallback();
+ protected final Map<Integer, ContextHubWrapperHidlCallback> mHidlCallbackMap =
+ new HashMap<>();
protected class ContextHubWrapperHidlCallback extends IContexthubCallback.Stub {
+ private final int mContextHubId;
+ private final ICallback mCallback;
+
+ ContextHubWrapperHidlCallback(int contextHubId, ICallback callback) {
+ mContextHubId = contextHubId;
+ mCallback = callback;
+ }
+
@Override
public void handleClientMsg(ContextHubMsg message) {
mCallback.handleNanoappMessage(
@@ -612,8 +629,9 @@ public abstract class IContextHubWrapper {
}
public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
- mCallback = callback;
- mHub.registerCallback(contextHubId, mHidlCallback);
+ mHidlCallbackMap.put(contextHubId,
+ new ContextHubWrapperHidlCallback(contextHubId, callback));
+ mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
}
public void onWifiMainSettingChanged(boolean enabled) {}
@@ -779,8 +797,9 @@ public abstract class IContextHubWrapper {
}
public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
- mCallback = callback;
- mHub.registerCallback_1_2(contextHubId, mHidlCallback);
+ mHidlCallbackMap.put(contextHubId,
+ new ContextHubWrapperHidlCallback(contextHubId, callback));
+ mHub.registerCallback_1_2(contextHubId, mHidlCallbackMap.get(contextHubId));
}
private void sendSettingChanged(byte setting, byte newValue) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bf50db85d984..a860d35b83dc 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2436,7 +2436,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
networkId, templateMeteredness, NetworkStats.ROAMING_ALL,
NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
- if (template.isPersistable()) {
+ if (NetworkPolicy.isTemplatePersistable(template)) {
mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule,
warningBytes, limitBytes, lastWarningSnooze,
lastLimitSnooze, metered, inferred));
@@ -2643,7 +2643,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
for (int i = 0; i < mNetworkPolicy.size(); i++) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
final NetworkTemplate template = policy.template;
- if (!template.isPersistable()) continue;
+ if (!NetworkPolicy.isTemplatePersistable(template)) continue;
out.startTag(null, TAG_NETWORK_POLICY);
writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 137fc85c1e72..20686322fd3d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7871,7 +7871,9 @@ public class NotificationManagerService extends SystemService {
int index = mToastQueue.indexOf(record);
if (index >= 0) {
- mToastQueue.remove(index);
+ ToastRecord toast = mToastQueue.remove(index);
+ mWindowManagerInternal.removeWindowToken(
+ toast.windowToken, true /* removeWindows */, toast.displayId);
}
record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1945ed054e21..2128beaa7322 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -146,7 +146,6 @@ import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationUtils;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.WatchedArrayMap;
-import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedLongSparseArray;
import com.android.server.utils.WatchedSparseBooleanArray;
import com.android.server.utils.WatchedSparseIntArray;
@@ -333,7 +332,7 @@ public class ComputerEngine implements Computer {
private final InstantAppRegistry mInstantAppRegistry;
private final ApplicationInfo mLocalAndroidApplication;
private final AppsFilter mAppsFilter;
- private final WatchedArraySet<String> mFrozenPackages;
+ private final WatchedArrayMap<String, Integer> mFrozenPackages;
// Immutable service attribute
private final String mAppPredictionServicePackage;
@@ -3580,7 +3579,7 @@ public class ComputerEngine implements Computer {
return PackageManagerService.PACKAGE_STARTABILITY_NOT_SYSTEM;
}
- if (mFrozenPackages.contains(packageName)) {
+ if (mFrozenPackages.containsKey(packageName)) {
return PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index c670e1fe69ff..47e94f37ec8c 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -504,6 +504,9 @@ final class DumpHelper {
ipw.println("(none)");
} else {
for (int i = 0; i < mPm.mFrozenPackages.size(); i++) {
+ ipw.print("package=");
+ ipw.print(mPm.mFrozenPackages.keyAt(i));
+ ipw.print(", refCounts=");
ipw.println(mPm.mFrozenPackages.valueAt(i));
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8e6746dca7fc..8573585c3c6b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2992,7 +2992,8 @@ final class InstallPackageHelper {
installPackageFromSystemLIF(stubPkg.getPath(),
mPm.mUserManager.getUserIds() /*allUserHandles*/,
null /*origUserHandles*/,
- true /*writeSettings*/);
+ true /*writeSettings*/,
+ Process.INVALID_UID /*previousAppId*/);
} catch (PackageManagerException pme) {
// Serious WTF; we have to be able to install the stub
Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(),
@@ -3118,8 +3119,11 @@ final class InstallPackageHelper {
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
try {
synchronized (mPm.mInstallLock) {
+ final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers;
+ final int previousAppId = disabledPs.getAppId() != deletedPs.getAppId()
+ ? deletedPs.getAppId() : Process.INVALID_UID;
installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
- outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
+ origUsers, writeSettings, previousAppId);
}
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
@@ -3160,7 +3164,8 @@ final class InstallPackageHelper {
*/
@GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
private void installPackageFromSystemLIF(@NonNull String codePathString,
- @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings)
+ @NonNull int[] allUserHandles, @Nullable int[] origUserHandles,
+ boolean writeSettings, int previousAppId)
throws PackageManagerException {
final File codePath = new File(codePathString);
@ParsingPackageUtils.ParseFlags int parseFlags =
@@ -3184,11 +3189,12 @@ final class InstallPackageHelper {
mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
setPackageInstalledForSystemPackage(pkg, allUserHandles,
- origUserHandles, writeSettings);
+ origUserHandles, writeSettings, previousAppId);
}
private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg,
- @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) {
+ @NonNull int[] allUserHandles, @Nullable int[] origUserHandles,
+ boolean writeSettings, int previousAppId) {
// writer
synchronized (mPm.mLock) {
PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3222,8 +3228,7 @@ final class InstallPackageHelper {
// The method below will take care of removing obsolete permissions and granting
// install permissions.
- mPm.mPermissionManager.onPackageInstalled(pkg,
- Process.INVALID_UID /* previousAppId */,
+ mPm.mPermissionManager.onPackageInstalled(pkg, previousAppId,
PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
UserHandle.USER_ALL);
for (final int userId : allUserHandles) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6e6773feb5f8..0777cdece8e7 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1685,14 +1685,12 @@ public class LauncherAppsService extends SystemService {
continue;
}
final String[] filteredPackagesWithoutExtras =
- getFilteredPackageNames(packages, cookie);
- // If all packages are filtered, skip notifying listener.
- if (ArrayUtils.isEmpty(filteredPackagesWithoutExtras)) {
- continue;
- }
+ getFilteredPackageNames(packagesNullExtras, cookie);
try {
- listener.onPackagesSuspended(user, filteredPackagesWithoutExtras,
- /* launcherExtras= */ null);
+ if (!ArrayUtils.isEmpty(filteredPackagesWithoutExtras)) {
+ listener.onPackagesSuspended(user, filteredPackagesWithoutExtras,
+ /* launcherExtras= */ null);
+ }
for (int idx = 0; idx < packagesWithExtras.size(); idx++) {
Pair<String, Bundle> packageExtraPair = packagesWithExtras.get(idx);
if (!isPackageVisibleToListener(packageExtraPair.first, cookie)) {
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 19ebb5d1caa2..652a9ae06124 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -130,7 +130,7 @@ public final class MovePackageHelper {
"Device admin cannot be moved");
}
- if (mPm.mFrozenPackages.contains(packageName)) {
+ if (mPm.mFrozenPackages.containsKey(packageName)) {
throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
"Failed to move already frozen package");
}
@@ -188,6 +188,7 @@ public final class MovePackageHelper {
for (int userId : installedUserIds) {
if (StorageManager.isFileEncryptedNativeOrEmulated()
&& !StorageManager.isUserKeyUnlocked(userId)) {
+ freezer.close();
throw new PackageManagerException(MOVE_FAILED_LOCKED_USER,
"User " + userId + " must be unlocked");
}
@@ -230,6 +231,7 @@ public final class MovePackageHelper {
final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) throws RemoteException {
+ freezer.close();
throw new IllegalStateException();
}
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index ecc92b7fa740..1e0a1f2ccf1f 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -31,8 +31,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
final class PackageFreezer implements AutoCloseable {
private final String mPackageName;
- private final boolean mWeFroze;
-
private final AtomicBoolean mClosed = new AtomicBoolean();
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -48,7 +46,7 @@ final class PackageFreezer implements AutoCloseable {
PackageFreezer(PackageManagerService pm) {
mPm = pm;
mPackageName = null;
- mWeFroze = false;
+ mClosed.set(true);
mCloseGuard.open("close");
}
@@ -58,7 +56,9 @@ final class PackageFreezer implements AutoCloseable {
mPackageName = packageName;
final PackageSetting ps;
synchronized (mPm.mLock) {
- mWeFroze = mPm.mFrozenPackages.add(mPackageName);
+ final int refCounts = mPm.mFrozenPackages
+ .getOrDefault(mPackageName, 0 /* defaultValue */) + 1;
+ mPm.mFrozenPackages.put(mPackageName, refCounts);
ps = mPm.mSettings.getPackageLPr(mPackageName);
}
if (ps != null) {
@@ -82,7 +82,11 @@ final class PackageFreezer implements AutoCloseable {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
synchronized (mPm.mLock) {
- if (mWeFroze) {
+ final int refCounts = mPm.mFrozenPackages
+ .getOrDefault(mPackageName, 0 /* defaultValue */) - 1;
+ if (refCounts > 0) {
+ mPm.mFrozenPackages.put(mPackageName, refCounts);
+ } else {
mPm.mFrozenPackages.remove(mPackageName);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6f8703bf5a5d..c6ec59a5811f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -249,7 +249,6 @@ import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
import com.android.server.utils.Watched;
import com.android.server.utils.WatchedArrayMap;
-import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedLongSparseArray;
import com.android.server.utils.WatchedSparseBooleanArray;
import com.android.server.utils.WatchedSparseIntArray;
@@ -646,15 +645,16 @@ public class PackageManagerService extends IPackageManager.Stub
final Settings mSettings;
/**
- * Set of package names that are currently "frozen", which means active
- * surgery is being done on the code/data for that package. The platform
- * will refuse to launch frozen packages to avoid race conditions.
+ * Map of package names to frozen counts that are currently "frozen",
+ * which means active surgery is being done on the code/data for that
+ * package. The platform will refuse to launch frozen packages to avoid
+ * race conditions.
*
* @see PackageFreezer
*/
@GuardedBy("mLock")
- final WatchedArraySet<String> mFrozenPackages = new WatchedArraySet<>();
- private final SnapshotCache<WatchedArraySet<String>> mFrozenPackagesSnapshot =
+ final WatchedArrayMap<String, Integer> mFrozenPackages = new WatchedArrayMap<>();
+ private final SnapshotCache<WatchedArrayMap<String, Integer>> mFrozenPackagesSnapshot =
new SnapshotCache.Auto(mFrozenPackages, mFrozenPackages,
"PackageManagerService.mFrozenPackages");
@@ -1016,7 +1016,7 @@ public class PackageManagerService extends IPackageManager.Stub
public final AppsFilter appsFilter;
public final ComponentResolver componentResolver;
public final PackageManagerService service;
- public final WatchedArraySet<String> frozenPackages;
+ public final WatchedArrayMap<String, Integer> frozenPackages;
Snapshot(int type) {
if (type == Snapshot.SNAPPED) {
@@ -7255,7 +7255,7 @@ public class PackageManagerService extends IPackageManager.Stub
*/
void checkPackageFrozen(String packageName) {
synchronized (mLock) {
- if (!mFrozenPackages.contains(packageName)) {
+ if (!mFrozenPackages.containsKey(packageName)) {
Slog.wtf(TAG, "Expected " + packageName + " to be frozen!", new Throwable());
}
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 9f3d19005de6..19f180f1ce4c 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -125,16 +125,10 @@ public final class SELinuxMMAC {
}
// Vendor mac permissions.
- // The filename has been renamed from nonplat_mac_permissions to
- // vendor_mac_permissions. Either of them should exist.
final File vendorMacPermission = new File(
Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml");
if (vendorMacPermission.exists()) {
sMacPermissions.add(vendorMacPermission);
- } else {
- // For backward compatibility.
- sMacPermissions.add(new File(Environment.getVendorDirectory(),
- "/etc/selinux/nonplat_mac_permissions.xml"));
}
// ODM mac permissions (optional).
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6c1ef2eda41d..a4f8087a2a07 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -198,6 +198,7 @@ final class DefaultPermissionGrantPolicy {
private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
static {
SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
}
private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 0958bcb6cc14..a3b6b8221843 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1252,7 +1252,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (op < 0) {
// Bg location is one-off runtime modifier permission and has no app op
if (sPlatformPermissions.contains(permission)
- && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+ && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
+ && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+ " with no app op defined!");
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index d1b99380b093..c9fd12219562 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
@@ -108,6 +109,7 @@ import android.util.DebugUtils;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -196,6 +198,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
/** All nearby devices permissions */
private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
+ // TODO: This is a placeholder. Replace with actual implementation
+ private static final List<String> NOTIFICATION_PERMISSIONS = new ArrayList<>();
+
/**
* All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are
* implicitly added to a package
@@ -4636,23 +4641,231 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
return true;
}
- private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId,
- @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
- @UserIdInt int[] userIds) {
- // If previousAppId is not Process.INVALID_UID, the package is performing a migration out
- // of a shared user group. Operations we need to do before calling updatePermissions():
- // - Retrieve the original uid permission state and create a copy of it as the new app's
- // uid state. The new permission state will be properly updated in updatePermissions().
- // - Remove the app from the original shared user group. Other apps in the shared
- // user group will perceive as if the original app is uninstalled.
- if (previousAppId != Process.INVALID_UID) {
- final PackageStateInternal ps =
- mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+ private boolean isEffectivelyGranted(PermissionState state) {
+ final int flags = state.getFlags();
+ final int denyMask = FLAG_PERMISSION_REVIEW_REQUIRED
+ | FLAG_PERMISSION_REVOKED_COMPAT
+ | FLAG_PERMISSION_ONE_TIME;
+
+ if ((flags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ return true;
+ } else if ((flags & FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ return (flags & FLAG_PERMISSION_REVOKED_COMPAT) == 0 && state.isGranted();
+ } else if ((flags & denyMask) != 0) {
+ return false;
+ } else {
+ return state.isGranted();
+ }
+ }
+
+ /**
+ * Merge srcState into destState. Return [granted, flags].
+ */
+ private Pair<Boolean, Integer> mergePermissionState(int appId,
+ PermissionState srcState, PermissionState destState) {
+ // This merging logic prioritizes the shared permission state (destState) over
+ // the current package's state (srcState), because an uninstallation of a previously
+ // unrelated app (the updated system app) should not affect the functionality of
+ // existing apps (other apps in the shared UID group).
+
+ final int userSettableMask = FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
+
+ final int defaultGrantMask = FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ | FLAG_PERMISSION_GRANTED_BY_ROLE;
+
+ final int priorityFixedMask = FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_POLICY_FIXED;
+
+ final int priorityMask = defaultGrantMask | priorityFixedMask;
+
+ final int destFlags = destState.getFlags();
+ final boolean destIsGranted = isEffectivelyGranted(destState);
+
+ final int srcFlags = srcState.getFlags();
+ final boolean srcIsGranted = isEffectivelyGranted(srcState);
+
+ final int combinedFlags = destFlags | srcFlags;
+
+ /* Merge flags */
+
+ int newFlags = 0;
+
+ // Inherit user set flags only from dest as we want to preserve the
+ // user preference of destState, not the one of the current package.
+ newFlags |= (destFlags & userSettableMask);
+
+ // Inherit all exempt flags
+ newFlags |= (combinedFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT);
+ // If no exempt flags are set, set APPLY_RESTRICTION
+ if ((newFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+ newFlags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+ }
+
+ // Inherit all priority flags
+ newFlags |= (combinedFlags & priorityMask);
+
+ // If no priority flags are set, inherit REVOKE_WHEN_REQUESTED
+ if ((combinedFlags & priorityMask) == 0) {
+ newFlags |= (combinedFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+ }
+
+ // Handle REVIEW_REQUIRED
+ if ((newFlags & priorityFixedMask) == 0) {
+ if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
+ // For notification permissions, inherit from both states
+ // if no priority FIXED flags are set
+ newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED);
+ } else if ((newFlags & priorityMask) == 0) {
+ // Else inherit from destState if no priority flags are set
+ newFlags |= (destFlags & FLAG_PERMISSION_REVIEW_REQUIRED);
+ }
+ }
+
+ /* Determine effective grant state */
+
+ final boolean effectivelyGranted;
+ if ((newFlags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ effectivelyGranted = true;
+ } else if ((destFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ // If this flag comes from destState, preserve its state
+ effectivelyGranted = destIsGranted;
+ } else if ((srcFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ effectivelyGranted = destIsGranted || srcIsGranted;
+ // If this flag comes from srcState, preserve flag only if
+ // there is no conflict
+ if (destIsGranted != srcIsGranted) {
+ newFlags &= ~FLAG_PERMISSION_POLICY_FIXED;
+ }
+ } else if ((destFlags & defaultGrantMask) != 0) {
+ // If a permission state has default grant flags and is not
+ // granted, this meant user has overridden the grant state.
+ // Respect the user's preference on destState.
+ // Due to this reason, if this flag comes from destState,
+ // preserve its state
+ effectivelyGranted = destIsGranted;
+ } else if ((srcFlags & defaultGrantMask) != 0) {
+ effectivelyGranted = destIsGranted || srcIsGranted;
+ } else if ((destFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
+ // Similar reason to defaultGrantMask, if this flag comes
+ // from destState, preserve its state
+ effectivelyGranted = destIsGranted;
+ } else if ((srcFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
+ effectivelyGranted = destIsGranted || srcIsGranted;
+ // If this flag comes from srcState, remove this flag if
+ // destState is already granted to prevent revocation.
+ if (destIsGranted) {
+ newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+ }
+ } else {
+ // If still not determined, fallback to destState.
+ effectivelyGranted = destIsGranted;
+ }
+
+ /* Post-processing / fix ups */
+
+ if (!effectivelyGranted) {
+ // If not effectively granted, inherit AUTO_REVOKED
+ newFlags |= (combinedFlags & FLAG_PERMISSION_AUTO_REVOKED);
+
+ // REVOKE_WHEN_REQUESTED make no sense when denied
+ newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+ } else {
+ // REVIEW_REQUIRED make no sense when granted
+ newFlags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
+
+ if (effectivelyGranted != destIsGranted) {
+ // Remove user set flags if state changes
+ newFlags &= ~userSettableMask;
+ }
+
+ // Fix permission state based on targetSdk of the shared UID
+ final boolean newGrantState;
+ if (!effectivelyGranted && isPermissionSplitFromNonRuntime(
+ srcState.getName(),
+ mPackageManagerInt.getUidTargetSdkVersion(appId))) {
+ // Even though effectively denied, it has to be set to granted
+ // for backwards compatibility
+ newFlags |= FLAG_PERMISSION_REVOKED_COMPAT;
+ newGrantState = true;
+ } else {
+ // Either it's effectively granted, or it targets a high enough API level
+ // to handle this permission properly
+ newGrantState = effectivelyGranted;
+ }
+
+ return new Pair<>(newGrantState, newFlags);
+ }
+
+ /**
+ * This method handles permission migration of packages leaving/joining shared UID
+ */
+ private void handleAppIdMigration(@NonNull AndroidPackage pkg, int previousAppId) {
+ final PackageStateInternal ps =
+ mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+
+ if (ps.getSharedUser() != null) {
+ // The package is joining a shared user group. This can only happen when a system
+ // app left shared UID with an update, and then the update is uninstalled.
+ // If no apps remain in its original shared UID group, clone the current
+ // permission state to the shared appId; or else, merge the current permission
+ // state into the shared UID state.
+
+ synchronized (mLock) {
+ for (final int userId : getAllUserIds()) {
+ final UserPermissionState userState = mState.getOrCreateUserState(userId);
+
+ // This is the permission state the package was using
+ final UidPermissionState uidState = userState.getUidState(previousAppId);
+ if (uidState == null) {
+ continue;
+ }
+
+ // This is the shared UID permission state the package wants to join
+ final UidPermissionState sharedUidState = userState.getUidState(ps.getAppId());
+ if (sharedUidState == null) {
+ // No apps remain in the shared UID group, clone permissions
+ userState.createUidStateWithExisting(ps.getAppId(), uidState);
+ } else {
+ final List<PermissionState> states = uidState.getPermissionStates();
+ final int count = states.size();
+ for (int i = 0; i < count; ++i) {
+ final PermissionState srcState = states.get(i);
+ final PermissionState destState =
+ sharedUidState.getPermissionState(srcState.getName());
+ if (destState != null) {
+ // Merge the 2 permission states
+ Pair<Boolean, Integer> newState =
+ mergePermissionState(ps.getAppId(), srcState, destState);
+ sharedUidState.putPermissionState(srcState.getPermission(),
+ newState.first, newState.second);
+ } else {
+ // Simply copy the permission state over
+ sharedUidState.putPermissionState(srcState.getPermission(),
+ srcState.isGranted(), srcState.getFlags());
+ }
+ }
+ }
+
+ // Remove permissions for the previous appId
+ userState.removeUidState(previousAppId);
+ }
+ }
+ } else {
+ // The package is migrating out of a shared user group.
+ // Operations we need to do before calling updatePermissions():
+ // - Retrieve the original uid permission state and create a copy of it as the
+ // new app's uid state. The new permission state will be properly updated in
+ // updatePermissions().
+ // - Remove the app from the original shared user group. Other apps in the shared
+ // user group will perceive as if the original app is uninstalled.
+
final List<AndroidPackage> origSharedUserPackages =
mPackageManagerInt.getPackagesForAppId(previousAppId);
synchronized (mLock) {
- // All users are affected
for (final int userId : getAllUserIds()) {
// Retrieve the original uid state
final UserPermissionState userState = mState.getUserState(userId);
@@ -4679,6 +4892,14 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
}
}
+ }
+
+ private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId,
+ @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
+ @UserIdInt int[] userIds) {
+ if (previousAppId != Process.INVALID_UID) {
+ handleAppIdMigration(pkg, previousAppId);
+ }
updatePermissions(pkg.getPackageName(), pkg);
for (final int userId : userIds) {
addAllowlistedRestrictedPermissionsInternal(pkg,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cde5273b38dd..6c17bf193e5b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4121,8 +4121,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean containsShowWhenLocked = containsShowWhenLockedWindow();
if (containsDismissKeyguard != mLastContainsDismissKeyguardWindow
|| containsShowWhenLocked != mLastContainsShowWhenLockedWindow) {
- mWmService.notifyKeyguardFlagsChanged(null /* callback */,
- getDisplayContent().getDisplayId());
+ mDisplayContent.notifyKeyguardFlagsChanged();
}
mLastContainsDismissKeyguardWindow = containsDismissKeyguard;
mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index b183281e0f52..bce288311e13 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -55,12 +55,12 @@ class ActivityRecordInputSink {
private final ActivityRecord mActivityRecord;
private final boolean mIsCompatEnabled;
+ private final String mName;
// Hold on to InputEventReceiver to prevent it from getting GCd.
private InputEventReceiver mInputEventReceiver;
private InputWindowHandleWrapper mInputWindowHandleWrapper;
- private final String mName = Integer.toHexString(System.identityHashCode(this))
- + " ActivityRecordInputSink";
+
private int mRapidTouchCount = 0;
private IBinder mToken;
private boolean mDisabled = false;
@@ -69,6 +69,8 @@ class ActivityRecordInputSink {
mActivityRecord = activityRecord;
mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
mActivityRecord.getUid());
+ mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
+ + mActivityRecord.mActivityComponent.getShortClassName();
}
public void applyChangesToSurfaceIfChanged(
@@ -91,11 +93,6 @@ class ActivityRecordInputSink {
ANIMATION_TYPE_APP_TRANSITION)) {
// TODO(b/208662670): Investigate if we can have feature active during animations.
mInputWindowHandleWrapper.setToken(null);
- } else if (mActivityRecord.mStartingData != null) {
- // TODO(b/208659130): Remove this special case
- // Don't block touches during splash screen. This is done to not show toasts for
- // touches passing through splash screens. b/171772640
- mInputWindowHandleWrapper.setToken(null);
} else {
mInputWindowHandleWrapper.setToken(mToken);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7fa98618ecb9..a9142ef85608 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -243,21 +243,6 @@ public abstract class ActivityTaskManagerInternal {
int startFlags, @Nullable Bundle options, int userId);
/**
- * Called when Keyguard flags might have changed.
- *
- * @param callback Callback to run after activity visibilities have been reevaluated. This can
- * be used from window manager so that when the callback is called, it's
- * guaranteed that all apps have their visibility updated accordingly.
- * @param displayId The id of the display where the keyguard flags changed.
- */
- public abstract void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId);
-
- /**
- * Called when the trusted state of Keyguard has changed.
- */
- public abstract void notifyKeyguardTrustedChanged();
-
- /**
* Called after virtual display Id is updated by
* {@link com.android.server.vr.Vr2dDisplay} with a specific
* {@param vr2dDisplayId}.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 173545c30291..15ebe2846513 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -63,7 +63,6 @@ import static android.provider.Settings.System.FONT_SCALE;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_WAKE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -5415,42 +5414,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
false /*validateIncomingUser*/);
}
- @Override
- public void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
- synchronized (mGlobalLock) {
-
- // We might change the visibilities here, so prepare an empty app transition which
- // might be overridden later if we actually change visibilities.
- final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
- if (dc == null) {
- return;
- }
- final boolean wasTransitionSet = dc.mAppTransition.isTransitionSet();
- if (!wasTransitionSet) {
- dc.prepareAppTransition(TRANSIT_NONE);
- }
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-
- // If there was a transition set already we don't want to interfere with it as we
- // might be starting it too early.
- if (!wasTransitionSet) {
- dc.executeAppTransition();
- }
- }
- if (callback != null) {
- callback.run();
- }
- }
-
- @Override
- public void notifyKeyguardTrustedChanged() {
- synchronized (mGlobalLock) {
- if (mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
- }
- }
- }
-
/**
* Called after virtual display Id is updated by
* {@link com.android.server.vr.Vr2dDisplay} with a specific
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e80a9b9f2a8e..8ede0160cdce 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -80,6 +80,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
@@ -5914,6 +5915,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
+ * Notifies that some Keyguard flags have changed and the visibilities of the activities may
+ * need to be reevaluated.
+ */
+ void notifyKeyguardFlagsChanged() {
+ if (!isKeyguardLocked()) {
+ // If keyguard is not locked, the change of flags won't affect activity visibility.
+ return;
+ }
+ // We might change the visibilities here, so prepare an empty app transition which might be
+ // overridden later if we actually change visibilities.
+ final boolean wasTransitionSet = mAppTransition.isTransitionSet();
+ if (!wasTransitionSet) {
+ prepareAppTransition(TRANSIT_NONE);
+ }
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+
+ // If there was a transition set already we don't want to interfere with it as we might be
+ // starting it too early.
+ if (!wasTransitionSet) {
+ executeAppTransition();
+ }
+ }
+
+ /**
* Check if the display has {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
*/
boolean canShowWithInsecureKeyguard() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4768b27a4f9e..5f7bd8dfe657 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1710,7 +1710,8 @@ public class DisplayPolicy {
if (mShowingDream != mLastShowingDream) {
mLastShowingDream = mShowingDream;
- mService.notifyShowingDreamChanged();
+ // Notify that isShowingDreamLw (which is checked in KeyguardController) has changed.
+ mDisplayContent.notifyKeyguardFlagsChanged();
}
mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
@@ -2377,8 +2378,7 @@ public class DisplayPolicy {
@VisibleForTesting
int updateLightNavigationBarLw(int appearance, WindowState navColorWin) {
- if (navColorWin == null || navColorWin.isDimming()
- || !isLightBarAllowed(navColorWin, Type.navigationBars())) {
+ if (navColorWin == null || !isLightBarAllowed(navColorWin, Type.navigationBars())) {
// Clear the light flag while not allowed.
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
return appearance;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c7b13eb96dfa..dcb28d25ee5d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1037,7 +1037,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) {
+ final boolean curDisplayInTransitNotAnimate =
+ // legacy transition
+ (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning())
+ // shell transition
+ || (curDisplay.mTransitionController.isShellTransitionsEnabled()
+ && !curDisplay.mTransitionController.isPlaying());
+ if (curDisplayInTransitNotAnimate) {
// We have finished the animation of an app transition. To do this, we have
// delayed a lot of operations like showing and hiding apps, moving apps in
// Z-order, etc.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c6948eee5a0c..873e18d0e5cf 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -25,6 +25,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -70,6 +71,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.view.animation.Animation;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -82,6 +84,8 @@ import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
/**
* Represents a logical transition.
@@ -90,6 +94,9 @@ import java.util.ArrayList;
class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
+ /** The default package for resources */
+ private static final String DEFAULT_PACKAGE = "android";
+
/** The transition has been created and is collecting, but hasn't formally started. */
private static final int STATE_COLLECTING = 0;
@@ -548,7 +555,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
- info.setAnimationOptions(mOverrideOptions);
+ if (mOverrideOptions != null) {
+ info.setAnimationOptions(mOverrideOptions);
+ }
// TODO(b/188669821): Move to animation impl in shell.
handleLegacyRecentsStartBehavior(dc, info);
@@ -1249,9 +1258,80 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
out.addChange(change);
}
+ final WindowManager.LayoutParams animLp =
+ getLayoutParamsForAnimationsStyle(type, sortedTargets);
+ if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
+ && animLp.windowAnimations != 0) {
+ // Don't send animation options if no windowAnimations have been set or if the we are
+ // running an app starting animation, in which case we don't want the app to be able to
+ // change its animation directly.
+ TransitionInfo.AnimationOptions animOptions =
+ TransitionInfo.AnimationOptions.makeAnimOptionsFromLayoutParameters(animLp);
+ out.setAnimationOptions(animOptions);
+ }
+
return out;
}
+ private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
+ ArrayList<WindowContainer> sortedTargets) {
+ // Find the layout params of the top-most application window that is part of the
+ // transition, which is what will control the animation theme.
+ final ArraySet<Integer> activityTypes = new ArraySet<>();
+ for (WindowContainer target : sortedTargets) {
+ if (target.asActivityRecord() != null) {
+ activityTypes.add(target.getActivityType());
+ } else if (target.asWindowToken() == null && target.asWindowState() == null) {
+ // We don't want app to customize animations that are not activity to activity.
+ // Activity-level transitions can only include activities, wallpaper and subwindows.
+ // Anything else is not a WindowToken nor a WindowState and is "higher" in the
+ // hierarchy which means we are no longer in an activity transition.
+ return null;
+ }
+ }
+ if (activityTypes.isEmpty()) {
+ // We don't want app to be able to customize transitions that are not activity to
+ // activity through the layout parameter animation style.
+ return null;
+ }
+ final ActivityRecord animLpActivity =
+ findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
+ final WindowState mainWindow = animLpActivity != null
+ ? animLpActivity.findMainWindow() : null;
+ return mainWindow != null ? mainWindow.mAttrs : null;
+ }
+
+ private static ActivityRecord findAnimLayoutParamsActivityRecord(
+ List<WindowContainer> sortedTargets,
+ @TransitionType int transit, ArraySet<Integer> activityTypes) {
+ // Remote animations always win, but fullscreen windows override non-fullscreen windows.
+ ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
+ w -> w.getRemoteAnimationDefinition() != null
+ && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+ if (result != null) {
+ return result;
+ }
+ result = lookForTopWindowWithFilter(sortedTargets,
+ w -> w.fillsParent() && w.findMainWindow() != null);
+ if (result != null) {
+ return result;
+ }
+ return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
+ }
+
+ private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets,
+ Predicate<ActivityRecord> filter) {
+ for (WindowContainer target : sortedTargets) {
+ final ActivityRecord activityRecord = target.asTaskFragment() != null
+ ? target.asTaskFragment().getTopNonFinishingActivity()
+ : target.asActivityRecord();
+ if (activityRecord != null && filter.test(activityRecord)) {
+ return activityRecord;
+ }
+ }
+ return null;
+ }
+
private static int getTaskRotationAnimation(@NonNull Task task) {
final ActivityRecord top = task.getTopVisibleActivity();
if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 4007661cf1a0..5e963cc5ae8e 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -135,8 +135,8 @@ class UnknownAppVisibilityController {
int state = mUnknownApps.get(activity);
if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) {
mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
- mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated,
- activity.getDisplayContent().getDisplayId());
+ mDisplayContent.notifyKeyguardFlagsChanged();
+ notifyVisibilitiesUpdated();
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index e24be378d29a..24493e2541e1 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -298,9 +298,9 @@ class WallpaperController {
}
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
- final Rect parentFrame = wallpaperWin.getParentFrame();
- final int dw = parentFrame.width();
- final int dh = parentFrame.height();
+ final Rect bounds = wallpaperWin.getLastReportedBounds();
+ final int dw = bounds.width();
+ final int dh = bounds.height();
int xOffset = 0;
int yOffset = 0;
@@ -448,6 +448,13 @@ class WallpaperController {
private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
WindowState target = mWallpaperTarget;
+ if (target == null && changingTarget.mToken.isVisible()
+ && changingTarget.mTransitionController.inTransition()) {
+ // If the wallpaper target was cleared during transition, still allows the visible
+ // window which may have been requested to be invisible to update the offset, e.g.
+ // zoom effect from home.
+ target = changingTarget;
+ }
if (target != null) {
if (target.mWallpaperX >= 0) {
mLastWallpaperX = target.mWallpaperX;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7e84dbbec311..bef7810f5d48 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3181,6 +3181,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/** Cheap way of doing cast and instanceof. */
+ WindowState asWindowState() {
+ return null;
+ }
+
+ /** Cheap way of doing cast and instanceof. */
ActivityRecord asActivityRecord() {
return null;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 86775f629df0..ded0dd7eb693 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3026,17 +3026,13 @@ public class WindowManagerService extends IWindowManager.Stub
aspectRatio);
}
- /**
- * Notifies window manager that {@link DisplayPolicy#isShowingDreamLw} has changed.
- */
- public void notifyShowingDreamChanged() {
- // TODO(multi-display): support show dream in multi-display.
- notifyKeyguardFlagsChanged(null /* callback */, DEFAULT_DISPLAY);
- }
-
@Override
public void notifyKeyguardTrustedChanged() {
- mAtmInternal.notifyKeyguardTrustedChanged();
+ synchronized (mGlobalLock) {
+ if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
+ mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ }
+ }
}
@Override
@@ -3088,15 +3084,6 @@ public class WindowManagerService extends IWindowManager.Stub
return getDefaultDisplayContentLocked().mAppTransition.isIdle();
}
- /**
- * Notifies activity manager that some Keyguard flags have changed and that it needs to
- * reevaluate the visibilities of the activities.
- * @param callback Runnable to be called when activity manager is done reevaluating visibilities
- */
- void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
- mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
- }
-
// -------------------------------------------------------------
// Misc IWindowSession methods
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0b9174210a19..f6729c552cc3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -844,6 +844,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
};
+ @Override
+ WindowState asWindowState() {
+ return this;
+ }
+
/**
* @see #setSurfaceTranslationY(int)
*/
@@ -2882,6 +2887,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mLastReportedConfiguration.getMergedConfiguration();
}
+ /** Returns the last window configuration bounds reported to the client. */
+ Rect getLastReportedBounds() {
+ final Rect bounds = getLastReportedConfiguration().windowConfiguration.getBounds();
+ return !bounds.isEmpty() ? bounds : getBounds();
+ }
+
void adjustStartingWindowFlags() {
if (mAttrs.type == TYPE_BASE_APPLICATION && mActivityRecord != null
&& mActivityRecord.mStartingWindow != null) {
@@ -5230,6 +5241,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Child window follows parent's scale.
return;
}
+ if (!isVisibleRequested() && !(mIsWallpaper && mToken.isVisible())) {
+ // Skip if it is requested to be invisible, but if it is wallpaper, it may be in
+ // transition that still needs to update the scale for zoom effect.
+ return;
+ }
float newHScale = mHScale * mGlobalScale * mWallpaperScale;
float newVScale = mVScale * mGlobalScale * mWallpaperScale;
if (mLastHScale != newHScale ||
@@ -5249,7 +5265,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateSurfacePositionNonOrganized();
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
- if (isVisibleRequested()) updateScaleIfNeeded();
+ updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
super.prepareSurfaces();
@@ -5275,11 +5291,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSurfacePosition);
if (mWallpaperScale != 1f) {
- DisplayInfo displayInfo = getDisplayInfo();
+ final Rect bounds = getLastReportedBounds();
Matrix matrix = mTmpMatrix;
matrix.setTranslate(mXOffset, mYOffset);
- matrix.postScale(mWallpaperScale, mWallpaperScale, displayInfo.logicalWidth / 2f,
- displayInfo.logicalHeight / 2f);
+ matrix.postScale(mWallpaperScale, mWallpaperScale, bounds.exactCenterX(),
+ bounds.exactCenterY());
matrix.getValues(mTmpMatrixArray);
mSurfacePosition.offset(Math.round(mTmpMatrixArray[Matrix.MTRANS_X]),
Math.round(mTmpMatrixArray[Matrix.MTRANS_Y]));
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c5a69e2f84b7..6aa632388ffd 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -2292,6 +2292,11 @@ static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, ji
sensorType));
}
+static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->getInputManager()->getDispatcher().cancelCurrentTouch();
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2371,6 +2376,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor},
{"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor},
{"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor},
+ {"nativeCancelCurrentTouch", "(J)V", (void*)nativeCancelCurrentTouch},
};
#define FIND_CLASS(var, className) \
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 266b65686141..df8953c318dc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7459,7 +7459,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ hasFullCrossUsersPermission(caller, userHandle) && canQueryAdminPolicy(caller));
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
diff --git a/services/proguard.flags b/services/proguard.flags
index 30dd6cf545b9..5d01d3e7f85c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -1,11 +1,21 @@
# TODO(b/196084106): Refine and optimize this configuration. Note that this
# configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`.
-keep,allowoptimization,allowaccessmodification class ** {
- *;
+ !synthetic *;
}
# Various classes subclassed in ethernet-service (avoid marking final).
-keep public class android.net.** { *; }
# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing).
--keep public class com.android.server.utils.Slogf { *; } \ No newline at end of file
+-keep public class com.android.server.utils.Slogf { *; }
+
+# Allows making private and protected methods/fields public as part of
+# optimization. This enables inlining of trivial getter/setter methods.
+-allowaccessmodification
+
+# Disallow accessmodification for soundtrigger classes. Logging via reflective
+# public member traversal can cause infinite loops. See b/210901706.
+-keep,allowoptimization class com.android.server.soundtrigger_middleware.** {
+ !synthetic *;
+}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 48a8b1bca99b..8538603d4180 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -59,6 +59,7 @@ android_test {
"mockingservicestests-utils-mockito",
"servicestests-core-utils",
"testables",
+ "kotlin-test",
// TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
"testng",
],
diff --git a/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml b/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml
new file mode 100644
index 000000000000..4720085e03fc
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<game-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="com.game.service.provider.GameSessionService"/>
diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml
new file mode 100644
index 000000000000..ebd5103a24ff
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<game-service/>
diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml
new file mode 100644
index 000000000000..8ee3cceee8ea
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<game-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gameSessionService="com.game.service.provider.GameSessionService"/>
diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml
new file mode 100644
index 000000000000..6bc0eac551bc
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<wrong-tag xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gameSessionService="com.game.service.provider.GameSessionService"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java
new file mode 100644
index 000000000000..060b773d7b7b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.os.UserHandle;
+
+import java.util.HashSet;
+
+/**
+ * Fake implementation of {@link GameClassifier} used for tests.
+ *
+ * By default, all packages are considers not games. A package may be marked as a game using
+ * {@link #recordGamePackage(String)}.
+ */
+final class FakeGameClassifier implements GameClassifier {
+ private final HashSet<String> mGamePackages = new HashSet<>();
+
+ /**
+ * Marks the given {@code packageName} as a game.
+ */
+ public void recordGamePackage(String packageName) {
+ mGamePackages.add(packageName);
+ }
+
+ @Override
+ public boolean isGame(@NonNull String packageName, UserHandle userHandle) {
+ return mGamePackages.contains(packageName);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java
new file mode 100644
index 000000000000..98142f5774a0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+
+/**
+ * Fake implementation of {@link GameServiceProviderInstance} used for tests.
+ */
+final class FakeGameServiceProviderInstance implements GameServiceProviderInstance {
+ private boolean mRunning;
+
+ @Override
+ public void start() {
+ mRunning = true;
+ }
+
+ @Override
+ public void stop() {
+ mRunning = false;
+ }
+
+ /**
+ * Returns {@code true} if the instance is currently running.
+ */
+ public boolean getIsRunning() {
+ return mRunning;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
new file mode 100644
index 000000000000..0ae509ec0fbc
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+
+import android.os.IInterface;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Fake implementation of {@link ServiceConnector<T>} used for tests.
+ *
+ * Tests provide a service instance via {@link #FakeServiceConnector(IInterface)} that will be
+ * connected to and used to fulfill service jobs.
+ */
+final class FakeServiceConnector<T extends IInterface> implements
+ ServiceConnector<T> {
+ private final T mService;
+ private boolean mIsConnected;
+ private int mConnectCount = 0;
+
+ FakeServiceConnector(T service) {
+ mService = service;
+ }
+
+ @Override
+ public boolean run(VoidJob<T> job) {
+ AndroidFuture<Void> unusedFuture = post(job);
+ return true;
+ }
+
+ @Override
+ public AndroidFuture<Void> post(VoidJob<T> job) {
+ markPossibleConnection();
+
+ return postForResult(job);
+ }
+
+ @Override
+ public <R> AndroidFuture<R> postForResult(Job<T, R> job) {
+ markPossibleConnection();
+
+ AndroidFuture<R> androidFuture = new AndroidFuture();
+ try {
+ androidFuture.complete(job.run(mService));
+ } catch (Exception ex) {
+ androidFuture.completeExceptionally(ex);
+ }
+ return androidFuture;
+ }
+
+ @Override
+ @SuppressWarnings("FutureReturnValueIgnored")
+ public <R> AndroidFuture<R> postAsync(Job<T, CompletableFuture<R>> job) {
+ markPossibleConnection();
+ AndroidFuture<R> androidFuture = new AndroidFuture();
+
+ try {
+ CompletableFuture<R> future = job.run(mService);
+ future.whenComplete((result, exception) -> {
+ if (exception != null) {
+ androidFuture.completeExceptionally(exception);
+ } else {
+ androidFuture.complete(result);
+ }
+ });
+ } catch (Exception ex) {
+ androidFuture.completeExceptionally(ex);
+ }
+
+ return androidFuture;
+ }
+
+ @Override
+ public AndroidFuture<T> connect() {
+ markPossibleConnection();
+ return AndroidFuture.completedFuture(mService);
+ }
+
+ @Override
+ public void unbind() {
+ mIsConnected = false;
+ }
+
+ private void markPossibleConnection() {
+ if (mIsConnected) {
+ return;
+ }
+
+ mConnectCount += 1;
+ mIsConnected = true;
+ }
+
+ /**
+ * Returns {@code true} if the underlying service is connected.
+ */
+ public boolean getIsConnected() {
+ return mIsConnected;
+ }
+
+ /**
+ * Returns the number of times a connection was established with the underlying service.
+ */
+ public int getConnectCount() {
+ return mConnectCount;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
new file mode 100644
index 000000000000..0545fde3e921
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ConcurrentUtils;
+import com.android.server.SystemService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+
+/** Unit tests for {@link GameServiceController}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class GameServiceControllerTest {
+ private static final UserHandle USER_HANDLE_10 = new UserHandle(10);
+ private static final UserHandle USER_HANDLE_11 = new UserHandle(11);
+ private static final SystemService.TargetUser USER_10 = user(10);
+ private static final SystemService.TargetUser USER_11 = user(11);
+ private static final String PROVIDER_A_PACKAGE_NAME = "com.provider.a";
+ private static final ComponentName PROVIDER_A_SERVICE_A =
+ new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceA");
+ private static final ComponentName PROVIDER_A_SERVICE_B =
+ new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceB");
+
+ private MockitoSession mMockingSession;
+ private GameServiceController mGameServiceManager;
+ @Mock
+ private GameServiceProviderSelector mMockGameServiceProviderSelector;
+ @Mock
+ private GameServiceProviderInstanceFactory mMockGameServiceProviderInstanceFactory;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mGameServiceManager = new GameServiceController(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ mMockGameServiceProviderSelector,
+ mMockGameServiceProviderInstanceFactory);
+ }
+
+ @After
+ public void tearDown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void notifyUserStarted_hasNotCompletedBoot_doesNothing() {
+ mGameServiceManager.notifyUserStarted(USER_10);
+
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ }
+
+ @Test
+ public void notifyUserStarted_createsAndStartsNewInstance() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_10, configurationA);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserStarted(USER_10);
+
+ verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isTrue();
+ }
+
+ @Test
+ public void notifyUserStarted_sameUser_doesNotCreateNewInstance() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_10, configurationA);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserStarted(USER_10);
+ mGameServiceManager.notifyUserStarted(USER_10);
+
+ verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isTrue();
+ }
+
+ @Test
+ public void notifyUserUnlocking_noForegroundUser_ignores() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_10, configurationA);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserUnlocking(USER_10);
+
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isFalse();
+ }
+
+ @Test
+ public void notifyUserUnlocking_sameAsForegroundUser_evaluatesProvider() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ seedNoConfigurationForUser(USER_10);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserStarted(USER_10);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_10, configurationA);
+ mGameServiceManager.notifyUserUnlocking(USER_10);
+
+ verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isTrue();
+ }
+
+ @Test
+ public void notifyUserUnlocking_differentFromForegroundUser_ignores() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ seedNoConfigurationForUser(USER_10);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserStarted(USER_10);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_11, configurationA);
+ mGameServiceManager.notifyUserUnlocking(USER_11);
+
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isFalse();
+ }
+
+ @Test
+ public void
+ notifyNewForegroundUser_differentUser_stopsPreviousInstanceAndThenStartsNewInstance() {
+ GameServiceProviderConfiguration configurationA =
+ new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ FakeGameServiceProviderInstance instanceA =
+ seedConfigurationForUser(USER_10, configurationA);
+ GameServiceProviderConfiguration configurationB =
+ new GameServiceProviderConfiguration(USER_HANDLE_11, PROVIDER_A_SERVICE_A,
+ PROVIDER_A_SERVICE_B);
+ FakeGameServiceProviderInstance instanceB = seedConfigurationForUser(USER_11,
+ configurationB);
+ InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB);
+
+ mGameServiceManager.onBootComplete();
+ mGameServiceManager.notifyUserStarted(USER_10);
+ mGameServiceManager.notifyNewForegroundUser(USER_11);
+
+ verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+ verify(mMockGameServiceProviderInstanceFactory).create(configurationB);
+ instancesInOrder.verify(instanceA).start();
+ instancesInOrder.verify(instanceA).stop();
+ instancesInOrder.verify(instanceB).start();
+ verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+ assertThat(instanceA.getIsRunning()).isFalse();
+ assertThat(instanceB.getIsRunning()).isTrue();
+ }
+
+ private void seedNoConfigurationForUser(SystemService.TargetUser user) {
+ when(mMockGameServiceProviderSelector.get(user)).thenReturn(null);
+ }
+
+ private FakeGameServiceProviderInstance seedConfigurationForUser(SystemService.TargetUser user,
+ GameServiceProviderConfiguration configuration) {
+ when(mMockGameServiceProviderSelector.get(user)).thenReturn(configuration);
+ FakeGameServiceProviderInstance instanceForConfiguration =
+ spy(new FakeGameServiceProviderInstance());
+ when(mMockGameServiceProviderInstanceFactory.create(configuration))
+ .thenReturn(instanceForConfiguration);
+
+ return instanceForConfiguration;
+ }
+
+ private static SystemService.TargetUser user(int userId) {
+ UserInfo userInfo = new UserInfo(userId, "", "", UserInfo.FLAG_FULL);
+ return new SystemService.TargetUser(userInfo);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java
deleted file mode 100644
index 8973a897e888..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.app;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.service.games.GameService;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.SystemService;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoSession;
-
-import java.util.List;
-
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public final class GameServiceControllerTests {
- @Mock
- private PackageManager mMockPackageManager;
- @Mock
- private Resources mMockResources;
- @Mock
- private Context mMockContext;
- private MockitoSession mMockingSession;
-
- private static UserInfo eligibleUserInfo(int uid) {
- return new UserInfo(uid, "", "", UserInfo.FLAG_FULL);
- }
-
- private static UserInfo managedUserInfo(int uid) {
- UserInfo userInfo = eligibleUserInfo(uid);
- userInfo.userType = UserManager.USER_TYPE_PROFILE_MANAGED;
- return userInfo;
- }
-
- private static ResolveInfo resolveInfo(ServiceInfo serviceInfo) {
- ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.serviceInfo = serviceInfo;
- return resolveInfo;
- }
-
- private static ServiceInfo serviceInfo(String packageName, String name, boolean isEnabled) {
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.packageName = packageName;
- applicationInfo.enabled = true;
-
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.applicationInfo = applicationInfo;
- serviceInfo.packageName = packageName;
- serviceInfo.name = name;
- serviceInfo.enabled = isEnabled;
- return serviceInfo;
- }
-
- private static SystemService.TargetUser managedTargetUser(int ineligibleUserId) {
- return new SystemService.TargetUser(managedUserInfo(ineligibleUserId));
- }
-
- private static SystemService.TargetUser eligibleTargetUser(int userId) {
- return new SystemService.TargetUser(eligibleUserInfo(userId));
- }
-
- private static UserHandle userWithId(int userId) {
- return argThat(userInfo -> userInfo.getIdentifier() == userId);
- }
-
- @Before
- public void setUp() {
- mMockingSession = mockitoSession()
- .initMocks(this)
- .startMocking();
- }
-
- @After
- public void tearDown() {
- mMockingSession.finishMocking();
- }
-
- @Test
- public void testStartConnectionOnBootWithNoUser() {
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
-
- verifyNoServiceBound();
- }
-
- @Test
- public void testStartConnectionOnBootWithManagedUser() {
- int userId = 12345;
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(managedTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyNoServiceBound();
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndNoSystemGamesServiceSet() {
- seedSystemGameServicePackageName("");
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(1000));
- gameServiceController.onBootComplete();
-
- verifyNoServiceBound();
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndSystemGamesServiceDoesNotExist() {
- int userId = 12345;
- String gameServicePackageName = "game.service.package";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of());
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyNoServiceBound();
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndSystemGamesServiceSet() {
- int userId = 12345;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent);
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndSystemGamesServiceNotEnabled() {
- int userId = 12345;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, false))));
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyNoServiceBound();
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasMultipleComponents() {
- int userId = 12345;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent1 = "game.service.package.example.GameService1";
- String gameServiceComponent2 = "game.service.package.example.GameService2";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, true)),
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true))));
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyServiceBoundForUserAndComponent(userId, gameServicePackageName,
- gameServiceComponent1);
- }
-
- @Test
- public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasDisabledComponent() {
- int userId = 12345;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent1 = "game.service.package.example.GameService1";
- String gameServiceComponent2 = "game.service.package.example.GameService2";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, false)),
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true))));
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
- gameServiceController.onBootComplete();
-
- verifyServiceBoundForUserAndComponent(userId, gameServicePackageName,
- gameServiceComponent2);
- }
-
- @Test
- public void testSwitchFromEligibleUserToEligibleUser() {
- int userId1 = 1;
- int userId2 = 2;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController = new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
-
- verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName,
- gameServiceComponent);
-
- gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2));
-
- verify(mMockContext).unbindService(any());
- verifyServiceBoundForUserAndComponent(userId2, gameServicePackageName,
- gameServiceComponent);
- }
-
- @Test
- public void testSwitchFromEligibleUserToIneligibleUser() {
- int eligibleUserId = 1;
- int ineligibleUserId = 2;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(eligibleTargetUser(eligibleUserId));
-
- verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName,
- gameServiceComponent);
-
- gameServiceController.notifyNewForegroundUser(managedTargetUser(ineligibleUserId));
-
- verify(mMockContext).unbindService(any());
- }
-
- @Test
- public void testSwitchFromIneligibleUserToEligibleUser() {
- int eligibleUserId = 1;
- int ineligibleUserId = 2;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController = new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(managedTargetUser(ineligibleUserId));
-
- verifyNoServiceBound();
-
- gameServiceController.notifyNewForegroundUser(eligibleTargetUser(eligibleUserId));
-
- verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName,
- gameServiceComponent);
- }
-
- @Test
- public void testMultipleRunningUsers() {
- int userId1 = 123;
- int userId2 = 456;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId2));
-
- verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName,
- gameServiceComponent);
- verifyServiceNotBoundForUser(userId2);
- verify(mMockContext, never()).unbindService(any());
- }
-
- @Test
- public void testForegroundUserStopped() {
- int userId = 123123;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
-
- verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent);
-
- gameServiceController.notifyUserStopped(eligibleTargetUser(userId));
-
- verify(mMockContext).unbindService(any());
- }
-
- @Test
- public void testNonForegroundUserStopped() {
- int userId1 = 123;
- int userId2 = 456;
- String gameServicePackageName = "game.service.package";
- String gameServiceComponent = "game.service.package.example.GameService";
- seedSystemGameServicePackageName(gameServicePackageName);
- seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of(
- resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
- seedGameServiceToBindSuccessfully();
-
- GameServiceController gameServiceController =
- new GameServiceController(mMockContext);
- InOrder inOrder = Mockito.inOrder(mMockContext);
-
- gameServiceController.onBootComplete();
- gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
-
- inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId1));
-
- gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2));
-
- inOrder.verify(mMockContext).unbindService(any());
- inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId2));
-
- gameServiceController.notifyUserStopped(eligibleTargetUser(userId1));
-
- inOrder.verify(mMockContext, never()).unbindService(any());
- }
-
- private void seedSystemGameServicePackageName(String gameServicePackageName) {
- when(mMockContext.getResources()).thenReturn(mMockResources);
- when(mMockResources.getString(com.android.internal.R.string.config_systemGameService))
- .thenReturn(gameServicePackageName);
- }
-
- private void seedGameServiceResolveInfos(String gameServicePackageName, int userId,
- List<ResolveInfo> resolveInfos) {
- when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- doReturn(resolveInfos)
- .when(mMockPackageManager).queryIntentServicesAsUser(
- argThat(intent ->
- intent != null
- && intent.getAction().equals(GameService.SERVICE_INTERFACE)
- && intent.getPackage().equals(gameServicePackageName)
- ),
- eq(PackageManager.MATCH_SYSTEM_ONLY),
- eq(userId));
- }
-
- private void seedGameServiceToBindSuccessfully() {
- when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
- }
-
- private void verifyNoServiceBound() {
- verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(), any());
- }
-
- private void verifyServiceBoundForUserAndComponent(int userId, String gameServicePackageName,
- String gameServiceComponent) {
- verify(mMockContext).bindServiceAsUser(
- argThat(intent -> intent.getAction().equals(GameService.SERVICE_INTERFACE)
- && intent.getComponent().getPackageName().equals(gameServicePackageName)
- && intent.getComponent().getClassName().equals(gameServiceComponent)),
- any(),
- anyInt(), argThat(userInfo -> userInfo.getIdentifier() == userId));
- }
-
- private void verifyServiceNotBoundForUser(int userId) {
- verify(mMockContext, never()).bindServiceAsUser(
- any(),
- any(),
- anyInt(), userWithId(userId));
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
new file mode 100644
index 000000000000..b6c706ed2730
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+
+import android.annotation.Nullable;
+import android.app.IActivityTaskManager;
+import android.app.ITaskStackListener;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.service.games.CreateGameSessionRequest;
+import android.service.games.IGameService;
+import android.service.games.IGameSession;
+import android.service.games.IGameSessionService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+
+/**
+ * Unit tests for the {@link GameServiceProviderInstanceImpl}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class GameServiceProviderInstanceImplTest {
+
+ private static final int USER_ID = 10;
+ private static final String APP_A_PACKAGE = "com.package.app.a";
+ private static final ComponentName APP_A_MAIN_ACTIVITY =
+ new ComponentName(APP_A_PACKAGE, "com.package.app.a.MainActivity");
+
+ private static final String GAME_A_PACKAGE = "com.package.game.a";
+ private static final ComponentName GAME_A_MAIN_ACTIVITY =
+ new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity");
+
+ private MockitoSession mMockingSession;
+ private GameServiceProviderInstance mGameServiceProviderInstance;
+ @Mock
+ private IActivityTaskManager mMockActivityTaskManager;
+ @Mock
+ private IGameService mMockGameService;
+ @Mock
+ private IGameSessionService mMockGameSessionService;
+ private FakeGameClassifier mFakeGameClassifier;
+ private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
+ private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
+ private ArrayList<ITaskStackListener> mTaskStackListeners;
+ private InOrder mInOrder;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException, RemoteException {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mInOrder = inOrder(mMockGameService, mMockGameSessionService);
+
+ mFakeGameClassifier = new FakeGameClassifier();
+ mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
+
+ mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService);
+ mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService);
+
+ mTaskStackListeners = new ArrayList<>();
+ doAnswer(invocation -> {
+ mTaskStackListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mMockActivityTaskManager).registerTaskStackListener(any());
+
+ doAnswer(invocation -> {
+ mTaskStackListeners.remove(invocation.getArgument(0));
+ return null;
+ }).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
+
+ mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
+ new UserHandle(USER_ID),
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ mFakeGameClassifier,
+ mMockActivityTaskManager,
+ mFakeGameServiceConnector,
+ mFakeGameSessionServiceConnector);
+ }
+
+ @After
+ public void tearDown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void start_startsGameSession() throws Exception {
+ mGameServiceProviderInstance.start();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void start_multipleTimes_startsGameSessionOnce() throws Exception {
+ mGameServiceProviderInstance.start();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void stop_neverStarted_doesNothing() throws Exception {
+ mGameServiceProviderInstance.stop();
+
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ mInOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void startAndStop_startsAndStopsGameSession() throws Exception {
+ mGameServiceProviderInstance.start();
+ mGameServiceProviderInstance.stop();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void startAndStop_multipleTimes_startsAndStopsGameSessionMultipleTimes()
+ throws Exception {
+ mGameServiceProviderInstance.start();
+ mGameServiceProviderInstance.stop();
+ mGameServiceProviderInstance.start();
+ mGameServiceProviderInstance.stop();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void stop_stopMultipleTimes_stopsGameSessionOnce() throws Exception {
+ mGameServiceProviderInstance.start();
+ mGameServiceProviderInstance.stop();
+ mGameServiceProviderInstance.stop();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void gameTaskStarted_neverStarted_doesNothing() throws Exception {
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void gameTaskRemoved_neverStarted_doesNothing() throws Exception {
+ dispatchTaskRemoved(10);
+
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void gameTaskStarted_afterStopped_doesNothing() throws Exception {
+ mGameServiceProviderInstance.start();
+ mGameServiceProviderInstance.stop();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void appTaskStarted_doesNothing() throws Exception {
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void taskStarted_nullComponentName_ignoresAndDoesNotCrash() throws Exception {
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, null);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void gameTaskStarted_createsGameSession() throws Exception {
+ CreateGameSessionRequest createGameSessionRequest =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession()
+ throws Exception {
+ CreateGameSessionRequest createGameSessionRequest =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ dispatchTaskRemoved(10);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void gameTaskRemoved_destroysGameSession() throws Exception {
+ CreateGameSessionRequest createGameSessionRequest =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+ dispatchTaskRemoved(10);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void gameTaskStarted_multipleTimes_createsMultipleGameSessions() throws Exception {
+ CreateGameSessionRequest createGameSessionRequest10 =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest10);
+
+ CreateGameSessionRequest createGameSessionRequest11 =
+ new CreateGameSessionRequest(11, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession11Future =
+ captureCreateGameSessionFuture(createGameSessionRequest11);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession11 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession11);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+ assertThat(gameSession11.mIsDestroyed).isFalse();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession()
+ throws Exception {
+ CreateGameSessionRequest createGameSessionRequest10 =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest10);
+
+ CreateGameSessionRequest createGameSessionRequest11 =
+ new CreateGameSessionRequest(11, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession11Future =
+ captureCreateGameSessionFuture(createGameSessionRequest11);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession11 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession11);
+
+ dispatchTaskRemoved(10);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isFalse();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void allGameTasksRemoved_destroysAllGameSessions() throws Exception {
+ CreateGameSessionRequest createGameSessionRequest10 =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest10);
+
+ CreateGameSessionRequest createGameSessionRequest11 =
+ new CreateGameSessionRequest(11, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession11Future =
+ captureCreateGameSessionFuture(createGameSessionRequest11);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession11 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession11);
+
+ dispatchTaskRemoved(10);
+ dispatchTaskRemoved(11);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void gameTasksCreated_afterAllPreviousSessionsDestroyed_createsSession()
+ throws Exception {
+ CreateGameSessionRequest createGameSessionRequest10 =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest10);
+
+ CreateGameSessionRequest createGameSessionRequest11 =
+ new CreateGameSessionRequest(11, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession11Future =
+ captureCreateGameSessionFuture(createGameSessionRequest11);
+
+ CreateGameSessionRequest createGameSessionRequest12 =
+ new CreateGameSessionRequest(12, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> unusedGameSession12Future =
+ captureCreateGameSessionFuture(createGameSessionRequest12);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+
+ dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession11 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession11);
+
+ dispatchTaskRemoved(10);
+ dispatchTaskRemoved(11);
+
+ dispatchTaskCreated(12, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession12 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession12);
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any());
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+ assertThat(gameSession12.mIsDestroyed).isFalse();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
+ CreateGameSessionRequest createGameSessionRequest10 =
+ new CreateGameSessionRequest(10, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession10Future =
+ captureCreateGameSessionFuture(createGameSessionRequest10);
+
+ CreateGameSessionRequest createGameSessionRequest11 =
+ new CreateGameSessionRequest(11, GAME_A_PACKAGE);
+ Supplier<AndroidFuture<IBinder>> gameSession11Future =
+ captureCreateGameSessionFuture(createGameSessionRequest11);
+
+ mGameServiceProviderInstance.start();
+ dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession10 = new IGameSessionStub();
+ gameSession10Future.get().complete(gameSession10);
+ dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY);
+ IGameSessionStub gameSession11 = new IGameSessionStub();
+ gameSession11Future.get().complete(gameSession11);
+ mGameServiceProviderInstance.stop();
+
+ mInOrder.verify(mMockGameService).connected();
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
+ mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
+ mInOrder.verify(mMockGameService).disconnected();
+ mInOrder.verifyNoMoreInteractions();
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+ assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+ }
+
+ private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture(
+ CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception {
+ final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>();
+ doAnswer(invocation -> {
+ gameSessionFuture.set(invocation.getArgument(1));
+ return null;
+ }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any());
+
+ return gameSessionFuture::get;
+ }
+
+ private void dispatchTaskRemoved(int taskId) {
+ dispatchTaskChangeEvent(taskStackListener -> {
+ taskStackListener.onTaskRemoved(taskId);
+ });
+ }
+
+ private void dispatchTaskCreated(int taskId, @Nullable ComponentName componentName) {
+ dispatchTaskChangeEvent(taskStackListener -> {
+ taskStackListener.onTaskCreated(taskId, componentName);
+ });
+ }
+
+ private void dispatchTaskChangeEvent(
+ ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) {
+ for (ITaskStackListener taskStackListener : mTaskStackListeners) {
+ taskStackListenerConsumer.accept(taskStackListener);
+ }
+ }
+
+ private static class IGameSessionStub extends IGameSession.Stub {
+ boolean mIsDestroyed = false;
+
+ @Override
+ public void destroy() {
+ mIsDestroyed = true;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
new file mode 100644
index 000000000000..59d0970f5934
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+import android.service.games.GameService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+
+/**
+ * Unit tests for the {@link GameServiceProviderSelectorImpl}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class GameServiceProviderSelectorImplTest {
+
+ private static final UserHandle USER_HANDLE_10 = new UserHandle(10);
+
+ private static final int GAME_SERVICE_META_DATA_RES_ID = 1337;
+ private static final String GAME_SERVICE_PACKAGE_NAME = "com.game.service.provider";
+ private static final String GAME_SERVICE_CLASS_NAME = "com.game.service.provider.GameService";
+ private static final ComponentName GAME_SERVICE_COMPONENT =
+ new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_CLASS_NAME);
+
+ private static final int GAME_SERVICE_B_META_DATA_RES_ID = 1338;
+ private static final String GAME_SERVICE_B_CLASS_NAME =
+ "com.game.service.provider.GameServiceB";
+ private static final ComponentName GAME_SERVICE_B_COMPONENT =
+ new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_B_CLASS_NAME);
+ private static final ServiceInfo GAME_SERVICE_B_WITH_OUT_META_DATA =
+ serviceInfo(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_B_CLASS_NAME);
+ private static final ServiceInfo GAME_SERVICE_B_SERVICE_INFO =
+ addGameServiceMetaData(GAME_SERVICE_B_WITH_OUT_META_DATA,
+ GAME_SERVICE_B_META_DATA_RES_ID);
+
+ private static final String GAME_SESSION_SERVICE_CLASS_NAME =
+ "com.game.service.provider.GameSessionService";
+ private static final ComponentName GAME_SESSION_SERVICE_COMPONENT =
+ new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SESSION_SERVICE_CLASS_NAME);
+ private static final ServiceInfo GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA =
+ serviceInfo(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_CLASS_NAME);
+ private static final ServiceInfo GAME_SERVICE_SERVICE_INFO =
+ addGameServiceMetaData(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA,
+ GAME_SERVICE_META_DATA_RES_ID);
+
+ @Mock
+ private PackageManager mMockPackageManager;
+ private Resources mSpyResources;
+ private MockitoSession mMockingSession;
+ private GameServiceProviderSelector mGameServiceProviderSelector;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mSpyResources = spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+
+ when(mMockPackageManager.getResourcesForApplication(anyString()))
+ .thenReturn(mSpyResources);
+ mGameServiceProviderSelector = new GameServiceProviderSelectorImpl(
+ mSpyResources,
+ mMockPackageManager);
+ }
+
+ @After
+ public void tearDown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void get_nullUser_returnsNull()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(null);
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_managedUser_returnsNull()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_noSystemGameService_returnsNull()
+ throws Exception {
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_noGameServiceProvidersAvailable_returnsNull()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10);
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_gameServiceProviderHasNoMetaData_returnsNull()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_gameSessionServiceDoesNotExist_returnsNull()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfoNotFound(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_metaDataWrongFirstTag_returnsNull() throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_wrong_first_tag.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ assertThat(gameServiceProviderConfiguration).isNull();
+ }
+
+ @Test
+ public void get_validGameServiceProviderAvailable_returnsGameServiceProvider()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
+ new GameServiceProviderConfiguration(USER_HANDLE_10,
+ GAME_SERVICE_COMPONENT,
+ GAME_SESSION_SERVICE_COMPONENT);
+ assertThat(gameServiceProviderConfiguration).isEqualTo(
+ expectedGameServiceProviderConfiguration);
+ }
+
+ @Test
+ public void get_multipleGameServiceProvidersAllValid_returnsFirstValidGameServiceProvider()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_B_SERVICE_INFO), resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_B_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
+ new GameServiceProviderConfiguration(USER_HANDLE_10,
+ GAME_SERVICE_B_COMPONENT,
+ GAME_SESSION_SERVICE_COMPONENT);
+ assertThat(gameServiceProviderConfiguration).isEqualTo(
+ expectedGameServiceProviderConfiguration);
+ }
+
+ @Test
+ public void get_multipleGameServiceProvidersSomeInvalid_returnsFirstValidGameServiceProvider()
+ throws Exception {
+ seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
+
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_B_SERVICE_INFO), resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+
+ GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
+ new GameServiceProviderConfiguration(USER_HANDLE_10,
+ GAME_SERVICE_COMPONENT,
+ GAME_SESSION_SERVICE_COMPONENT);
+ assertThat(gameServiceProviderConfiguration).isEqualTo(
+ expectedGameServiceProviderConfiguration);
+ }
+
+ private void seedSystemGameServicePackageName(String gameServicePackageName) {
+ when(mSpyResources.getString(com.android.internal.R.string.config_systemGameService))
+ .thenReturn(gameServicePackageName);
+ }
+
+ private void seedGameServiceResolveInfos(
+ String gameServicePackageName,
+ UserHandle userHandle,
+ ResolveInfo... resolveInfos) {
+ doReturn(ImmutableList.copyOf(resolveInfos))
+ .when(mMockPackageManager).queryIntentServicesAsUser(
+ argThat(intent ->
+ intent != null
+ && intent.getAction().equals(
+ GameService.ACTION_GAME_SERVICE)
+ && intent.getPackage().equals(gameServicePackageName)
+ ),
+ anyInt(),
+ eq(userHandle.getIdentifier()));
+ }
+
+ private void seedServiceServiceInfo(ComponentName componentName) throws Exception {
+ when(mMockPackageManager.getServiceInfo(eq(componentName), anyInt()))
+ .thenReturn(
+ serviceInfo(componentName.getPackageName(), componentName.getClassName()));
+ }
+
+ private void seedServiceServiceInfoNotFound(ComponentName componentName) throws Exception {
+ when(mMockPackageManager.getServiceInfo(eq(componentName), anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ }
+
+ private void seedGameServiceMetaDataFromFile(String packageName, int resId, String fileName)
+ throws Exception {
+
+ AssetManager assetManager =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ XmlResourceParser xmlResourceParser =
+ assetManager.openXmlResourceParser(fileName);
+
+ when(mMockPackageManager.getXml(eq(packageName), eq(resId), any()))
+ .thenReturn(xmlResourceParser);
+ }
+
+ private static UserInfo eligibleUserInfo(int uid) {
+ return new UserInfo(uid, "", "", UserInfo.FLAG_FULL);
+ }
+
+ private static UserInfo managedUserInfo(int uid) {
+ UserInfo userInfo = eligibleUserInfo(uid);
+ userInfo.userType = UserManager.USER_TYPE_PROFILE_MANAGED;
+ return userInfo;
+ }
+
+ private static ResolveInfo resolveInfo(ServiceInfo serviceInfo) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ return resolveInfo;
+ }
+
+ private static ServiceInfo serviceInfo(String packageName, String name) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ applicationInfo.enabled = true;
+
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.applicationInfo = applicationInfo;
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = name;
+ serviceInfo.enabled = true;
+
+ return serviceInfo;
+ }
+
+ private static ServiceInfo addGameServiceMetaData(ServiceInfo serviceInfo, int resId) {
+ if (serviceInfo.metaData == null) {
+ serviceInfo.metaData = new Bundle();
+ }
+ serviceInfo.metaData.putInt(GameService.SERVICE_META_DATA, resId);
+
+ return serviceInfo;
+ }
+
+ private static SystemService.TargetUser managedTargetUser(UserHandle userHandle) {
+ return new SystemService.TargetUser(managedUserInfo(userHandle.getIdentifier()));
+ }
+
+ private static SystemService.TargetUser eligibleTargetUser(UserHandle userHandle) {
+ return new SystemService.TargetUser(eligibleUserInfo(userHandle.getIdentifier()));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
new file mode 100644
index 000000000000..edbfecc41b04
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm
+
+import android.os.Build
+import com.android.server.testutils.any
+import com.android.server.testutils.spy
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import kotlin.test.assertFailsWith
+
+@RunWith(JUnit4::class)
+class PackageFreezerTest {
+
+ companion object {
+ const val TEST_PACKAGE = "com.android.test.package"
+ const val TEST_REASON = "test reason"
+ const val TEST_USER_ID = 0
+ }
+
+ @Rule
+ @JvmField
+ val rule = MockSystemRule()
+
+ lateinit var pms: PackageManagerService
+
+ private fun createPackageManagerService(vararg stageExistingPackages: String):
+ PackageManagerService {
+ stageExistingPackages.forEach {
+ rule.system().stageScanExistingPackage(it, 1L,
+ rule.system().dataAppDirectory)
+ }
+ var pms = PackageManagerService(rule.mocks().injector,
+ false /*coreOnly*/,
+ false /*factoryTest*/,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /*isEngBuild*/,
+ false /*isUserDebugBuild*/,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL,
+ false /*snapshotEnabled*/)
+ rule.system().validateFinalState()
+ return pms
+ }
+
+ private fun frozenMessage(packageName: String) = "Package $packageName is currently frozen!"
+
+ private fun <T : Throwable> assertThrowContainsMessage(
+ exceptionClass: kotlin.reflect.KClass<T>,
+ message: String,
+ block: () -> Unit
+ ) {
+ assertThat(assertFailsWith(exceptionClass, block).message).contains(message)
+ }
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ rule.system().stageNominalSystemState()
+ pms = spy(createPackageManagerService(TEST_PACKAGE))
+ whenever(pms.killApplication(any(), any(), any(), any()))
+ }
+
+ @Test
+ fun freezePackage() {
+ val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms)
+ verify(pms, times(1))
+ .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON))
+
+ assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) {
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ freezer.close()
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ @Test
+ fun freezePackage_twice() {
+ val freezer1 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms)
+ val freezer2 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms)
+ verify(pms, times(2))
+ .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON))
+
+ assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) {
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ freezer1.close()
+ assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) {
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ freezer2.close()
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ @Test
+ fun freezePackage_withoutClosing() {
+ var freezer: PackageFreezer? = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms)
+ verify(pms, times(1))
+ .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON))
+
+ assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) {
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+
+ freezer = null
+ System.gc()
+ System.runFinalization()
+
+ pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID)
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING
new file mode 100644
index 000000000000..13e255fe4ab8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 80f272905143..e756124e2e99 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -97,6 +97,10 @@
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+ <queries>
+ <package android:name="com.android.servicestests.apps.suspendtestapp" />
+ </queries>
+
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
android:targetSdkVersion="26"/>
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index d926dcba54a3..b2854ceb1017 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -36,6 +36,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import org.junit.Before;
@@ -303,6 +305,51 @@ public class CompatConfigTest {
assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isTrue();
}
+ @Test
+ public void testInstallerCanAddOverridesForMultiplePackages() throws Exception {
+ final String packageName1 = "com.some.package1";
+ final String packageName2 = "com.some.package2";
+ final long disabledChangeId1 = 1234L;
+ final long disabledChangeId2 = 1235L;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledOverridableChangeWithId(disabledChangeId1)
+ .addDisabledOverridableChangeWithId(disabledChangeId2)
+ .build();
+ ApplicationInfo applicationInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(packageName1)
+ .build();
+ ApplicationInfo applicationInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(packageName2)
+ .build();
+ PackageManager packageManager = mock(PackageManager.class);
+ when(mContext.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.getApplicationInfo(eq(packageName1), anyInt()))
+ .thenReturn(applicationInfo1);
+ when(packageManager.getApplicationInfo(eq(packageName2), anyInt()))
+ .thenReturn(applicationInfo2);
+
+ // Force the validator to prevent overriding non-overridable changes by using a user build.
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+ Map<Long, PackageOverride> overrides1 = new HashMap<>();
+ overrides1.put(disabledChangeId1, new PackageOverride.Builder().setEnabled(true).build());
+ Map<Long, PackageOverride> overrides2 = new HashMap<>();
+ overrides2.put(disabledChangeId1, new PackageOverride.Builder().setEnabled(true).build());
+ overrides2.put(disabledChangeId2, new PackageOverride.Builder().setEnabled(true).build());
+ Map<String, CompatibilityOverrideConfig> packageNameToOverrides = new HashMap<>();
+ packageNameToOverrides.put(packageName1, new CompatibilityOverrideConfig(overrides1));
+ packageNameToOverrides.put(packageName2, new CompatibilityOverrideConfig(overrides2));
+ CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig(
+ packageNameToOverrides);
+
+ compatConfig.addAllPackageOverrides(config, /* skipUnknownChangeIds */ true);
+
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo2)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo2)).isTrue();
+ }
+
@Test
public void testPreventInstallerSetNonOverridable() throws Exception {
@@ -641,6 +688,73 @@ public class CompatConfigTest {
}
@Test
+ public void testInstallerCanRemoveOverridesForMultiplePackages() throws Exception {
+ final String packageName1 = "com.some.package1";
+ final String packageName2 = "com.some.package2";
+ final long disabledChangeId1 = 1234L;
+ final long disabledChangeId2 = 1235L;
+ final long enabledChangeId = 1236L;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledOverridableChangeWithId(disabledChangeId1)
+ .addDisabledOverridableChangeWithId(disabledChangeId2)
+ .addEnabledOverridableChangeWithId(enabledChangeId)
+ .build();
+ ApplicationInfo applicationInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(packageName1)
+ .build();
+ ApplicationInfo applicationInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(packageName2)
+ .build();
+ PackageManager packageManager = mock(PackageManager.class);
+ when(mContext.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.getApplicationInfo(eq(packageName1), anyInt()))
+ .thenReturn(applicationInfo1);
+ when(packageManager.getApplicationInfo(eq(packageName2), anyInt()))
+ .thenReturn(applicationInfo2);
+
+ assertThat(compatConfig.addOverride(disabledChangeId1, packageName1, true)).isTrue();
+ assertThat(compatConfig.addOverride(disabledChangeId2, packageName1, true)).isTrue();
+ assertThat(compatConfig.addOverride(enabledChangeId, packageName1, false)).isTrue();
+ assertThat(compatConfig.addOverride(disabledChangeId1, packageName2, true)).isTrue();
+ assertThat(compatConfig.addOverride(disabledChangeId2, packageName2, true)).isTrue();
+ assertThat(compatConfig.addOverride(enabledChangeId, packageName2, false)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isFalse();
+
+ // Force the validator to prevent overriding non-overridable changes by using a user build.
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+ Set<Long> overridesToRemove1 = new HashSet<>();
+ overridesToRemove1.add(disabledChangeId1);
+ overridesToRemove1.add(enabledChangeId);
+ Set<Long> overridesToRemove2 = new HashSet<>();
+ overridesToRemove2.add(disabledChangeId1);
+ overridesToRemove2.add(disabledChangeId2);
+ Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove =
+ new HashMap<>();
+ packageNameToOverridesToRemove.put(packageName1,
+ new CompatibilityOverridesToRemoveConfig(overridesToRemove1));
+ packageNameToOverridesToRemove.put(packageName2,
+ new CompatibilityOverridesToRemoveConfig(overridesToRemove2));
+ CompatibilityOverridesToRemoveByPackageConfig config =
+ new CompatibilityOverridesToRemoveByPackageConfig(packageNameToOverridesToRemove);
+
+ compatConfig.removeAllPackageOverrides(config);
+
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo2)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo2)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo2)).isFalse();
+ }
+
+ @Test
public void testPreventInstallerRemoveNonOverridable() throws Exception {
final long disabledChangeId1 = 1234L;
final long disabledChangeId2 = 1235L;
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 2290ef79da78..398148ff4d3b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -25,23 +25,16 @@ import static android.app.AppOpsManager.opToName;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
import android.app.AppGlobals;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
-import android.content.res.Resources;
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Handler;
@@ -50,13 +43,6 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -65,7 +51,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
-import com.android.servicestests.apps.suspendtestapp.SuspendTestActivity;
import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver;
import org.junit.After;
@@ -76,7 +61,6 @@ import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -84,8 +68,6 @@ import java.util.concurrent.atomic.AtomicReference;
@LargeTest
@FlakyTest
public class SuspendPackagesTest {
- private static final String TAG = SuspendPackagesTest.class.getSimpleName();
- private static final String TEST_APP_LABEL = "Suspend Test App";
private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME;
private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME};
@@ -105,75 +87,11 @@ public class SuspendPackagesTest {
public static final String EXTRA_RECEIVED_PACKAGE_NAME =
SuspendPackagesTest.INSTRUMENTATION_PACKAGE + ".extra.RECEIVED_PACKAGE_NAME";
-
private Context mContext;
private PackageManager mPackageManager;
private LauncherApps mLauncherApps;
private Handler mReceiverHandler;
- private AppCommunicationReceiver mAppCommsReceiver;
private StubbedCallback mTestCallback;
- private UiDevice mUiDevice;
- private ComponentName mDeviceAdminComponent;
- private boolean mPoSet;
- private boolean mDoSet;
-
- private static final class AppCommunicationReceiver extends BroadcastReceiver {
- private Context context;
- private boolean registered;
- private SynchronousQueue<Intent> intentQueue = new SynchronousQueue<>();
-
- AppCommunicationReceiver(Context context) {
- this.context = context;
- }
-
- void register(Handler handler, String... actions) {
- registered = true;
- final IntentFilter intentFilter = new IntentFilter();
- for (String action : actions) {
- intentFilter.addAction(action);
- }
- context.registerReceiver(this, intentFilter, null, handler);
- }
-
- void unregister() {
- if (registered) {
- context.unregisterReceiver(this);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "AppCommunicationReceiver#onReceive: " + intent.getAction());
- try {
- intentQueue.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException ie) {
- throw new RuntimeException("Receiver thread interrupted", ie);
- }
- }
-
- Intent pollForIntent(long secondsToWait) {
- if (!registered) {
- throw new IllegalStateException("Receiver not registered");
- }
- final Intent intent;
- try {
- intent = intentQueue.poll(secondsToWait, TimeUnit.SECONDS);
- } catch (InterruptedException ie) {
- throw new RuntimeException("Interrupted while waiting for app broadcast", ie);
- }
- return intent;
- }
-
- void drainPendingBroadcasts() {
- while (pollForIntent(5) != null) ;
- }
-
- Intent receiveIntentFromApp() {
- final Intent intentReceived = pollForIntent(5);
- assertNotNull("No intent received from app within 5 seconds", intentReceived);
- return intentReceived;
- }
- }
@Before
public void setUp() {
@@ -181,9 +99,6 @@ public class SuspendPackagesTest {
mPackageManager = mContext.getPackageManager();
mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
mReceiverHandler = new Handler(Looper.getMainLooper());
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- mDeviceAdminComponent = new ComponentName(mContext,
- "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1");
IPackageManager ipm = AppGlobals.getPackageManager();
try {
// Otherwise implicit broadcasts will not be delivered.
@@ -192,31 +107,6 @@ public class SuspendPackagesTest {
e.rethrowAsRuntimeException();
}
unsuspendTestPackage();
- mAppCommsReceiver = new AppCommunicationReceiver(mContext);
- }
-
- /**
- * Care should be taken when used with {@link #mAppCommsReceiver} in the same test as both use
- * the same handler.
- */
- private Bundle requestAppAction(String action) throws InterruptedException {
- final AtomicReference<Bundle> result = new AtomicReference<>();
- final CountDownLatch receiverLatch = new CountDownLatch(1);
- final ComponentName testReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
- SuspendTestReceiver.class.getCanonicalName());
- final Intent broadcastIntent = new Intent(action)
- .setComponent(testReceiverComponent)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- result.set(getResultExtras(true));
- receiverLatch.countDown();
- }
- }, mReceiverHandler, 0, null, null);
-
- assertTrue("Test receiver timed out ", receiverLatch.await(5, TimeUnit.SECONDS));
- return result.get();
}
private PersistableBundle getExtras(String keyPrefix, long lval, String sval, double dval) {
@@ -240,14 +130,6 @@ public class SuspendPackagesTest {
assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
}
- private void startTestAppActivity() {
- final Intent testActivity = new Intent()
- .setComponent(new ComponentName(TEST_APP_PACKAGE_NAME,
- SuspendTestActivity.class.getCanonicalName()))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(testActivity);
- }
-
private static boolean areSameExtras(BaseBundle expected, BaseBundle received) {
if (expected != null) {
expected.get(""); // hack to unparcel the bundles.
@@ -265,93 +147,6 @@ public class SuspendPackagesTest {
}
@Test
- public void testIsPackageSuspended() throws Exception {
- suspendTestPackage(null, null, null);
- assertTrue("isPackageSuspended is false",
- mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
- }
-
- @Test
- public void testSuspendedStateFromApp() throws Exception {
- Bundle resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
- assertFalse(resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED, true));
- assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
-
- final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2);
- suspendTestPackage(appExtras, null, null);
-
- resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
- assertTrue("resultFromApp:suspended is false",
- resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED));
- final Bundle receivedAppExtras =
- resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS);
- assertSameExtras("Received app extras different to the ones supplied",
- appExtras, receivedAppExtras);
- }
-
- @Test
- public void testMyPackageSuspendedUnsuspended() {
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED,
- ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
- mAppCommsReceiver.drainPendingBroadcasts();
- final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1);
- suspendTestPackage(appExtras, null, null);
- Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
- assertSameExtras("Received app extras different to the ones supplied", appExtras,
- intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
- unsuspendTestPackage();
- intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("MY_PACKAGE_UNSUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
- }
-
- @Test
- public void testUpdatingAppExtras() {
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
- final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1,
- "1", 0.1);
- suspendTestPackage(extras1, null, null);
- Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
- assertSameExtras("Received app extras different to the ones supplied", extras1,
- intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
- final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
- "2", 0.2);
- suspendTestPackage(extras2, null, null);
- intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
- ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
- assertSameExtras("Received app extras different to the updated extras", extras2,
- intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
- }
-
- @Test
- public void testCannotSuspendSelf() {
- final String[] unchangedPkgs = mPackageManager.setPackagesSuspended(
- new String[]{mContext.getOpPackageName()}, true, null, null,
- (SuspendDialogInfo) null);
- assertTrue(unchangedPkgs.length == 1);
- assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]);
- }
-
- @Test
- public void testActivityStoppedOnSuspend() {
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_TEST_ACTIVITY_STARTED,
- ACTION_REPORT_TEST_ACTIVITY_STOPPED);
- startTestAppActivity();
- Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("Test activity start not reported",
- ACTION_REPORT_TEST_ACTIVITY_STARTED, intentFromApp.getAction());
- suspendTestPackage(null, null, null);
- intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals("Test activity stop not reported on suspending the test app",
- ACTION_REPORT_TEST_ACTIVITY_STOPPED, intentFromApp.getAction());
- }
-
- @Test
public void testGetLauncherExtrasNonNull() {
final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras(
TEST_APP_PACKAGE_NAME, mContext.getUser());
@@ -383,14 +178,15 @@ public class SuspendPackagesTest {
public void testOnPackagesSuspendedNewAndOld() throws InterruptedException {
final PersistableBundle suppliedExtras = getExtras(
"testOnPackagesSuspendedNewAndOld", 2, "2", 0.2);
- final AtomicReference<String> overridingBothCallbackResult = new AtomicReference<>("");
- final CountDownLatch twoCallbackLatch = new CountDownLatch(2);
+ final AtomicReference<String> error = new AtomicReference<>("");
+ final CountDownLatch rightCallbackLatch = new CountDownLatch(1);
+ final CountDownLatch wrongCallbackLatch = new CountDownLatch(1);
mTestCallback = new StubbedCallback() {
@Override
public void onPackagesSuspended(String[] packageNames, UserHandle user) {
- overridingBothCallbackResult.set(overridingBothCallbackResult.get()
+ error.set(error.get()
+ "Old callback called even when the new one is overriden. ");
- twoCallbackLatch.countDown();
+ wrongCallbackLatch.countDown();
}
@Override
@@ -411,17 +207,16 @@ public class SuspendPackagesTest {
errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras
+ ", received: " + launcherExtras + ". ");
}
- overridingBothCallbackResult.set(overridingBothCallbackResult.get()
+ error.set(error.get()
+ errorString.toString());
- twoCallbackLatch.countDown();
+ rightCallbackLatch.countDown();
}
};
mLauncherApps.registerCallback(mTestCallback, mReceiverHandler);
suspendTestPackage(null, suppliedExtras, null);
- assertFalse("Both callbacks were invoked", twoCallbackLatch.await(5, TimeUnit.SECONDS));
- twoCallbackLatch.countDown();
- assertTrue("No callback was invoked", twoCallbackLatch.await(2, TimeUnit.SECONDS));
- final String result = overridingBothCallbackResult.get();
+ assertFalse("Wrong callback was invoked", wrongCallbackLatch.await(5, TimeUnit.SECONDS));
+ assertTrue("Right callback wasn't invoked", rightCallbackLatch.await(2, TimeUnit.SECONDS));
+ final String result = error.get();
assertTrue("Callbacks did not complete as expected: " + result, result.isEmpty());
}
@@ -457,103 +252,6 @@ public class SuspendPackagesTest {
assertTrue("Callback did not complete as expected: " + result, result.isEmpty());
}
- private void turnScreenOn() throws Exception {
- if (!mUiDevice.isScreenOn()) {
- mUiDevice.wakeUp();
- }
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.dismissKeyguard(null, null);
- }
-
- @Test
- public void testInterceptorActivity() throws Exception {
- turnScreenOn();
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED,
- ACTION_REPORT_TEST_ACTIVITY_STARTED);
- final String testMessage = "This is a test message to report suspension of %1$s";
- suspendTestPackage(null, null,
- new SuspendDialogInfo.Builder().setMessage(testMessage).build());
- startTestAppActivity();
- assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2));
- assertNotNull("Given dialog message not shown", mUiDevice.wait(
- Until.findObject(By.text(String.format(testMessage, TEST_APP_LABEL))), 5000));
- final String buttonText = mContext.getResources().getString(Resources.getSystem()
- .getIdentifier("app_suspended_more_details", "string", "android"));
- final UiObject2 moreDetailsButton = mUiDevice.findObject(
- By.clickable(true).text(buttonText));
- assertNotNull(buttonText + " button not shown", moreDetailsButton);
- moreDetailsButton.click();
- final Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals(buttonText + " activity start not reported",
- ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, intentFromApp.getAction());
- final String receivedPackageName = intentFromApp.getStringExtra(
- EXTRA_RECEIVED_PACKAGE_NAME);
- assertEquals("Wrong package name received by " + buttonText + " activity",
- TEST_APP_PACKAGE_NAME, receivedPackageName);
- }
-
- private boolean setProfileOwner() throws IOException {
- final String result = mUiDevice.executeShellCommand("dpm set-profile-owner --user cur "
- + mDeviceAdminComponent.flattenToString());
- return mPoSet = result.trim().startsWith("Success");
- }
-
- private boolean setDeviceOwner() throws IOException {
- final String result = mUiDevice.executeShellCommand("dpm set-device-owner --user cur "
- + mDeviceAdminComponent.flattenToString());
- return mDoSet = result.trim().startsWith("Success");
- }
-
- private void removeProfileOrDeviceOwner() throws IOException {
- if (mPoSet || mDoSet) {
- mUiDevice.executeShellCommand("dpm remove-active-admin --user cur "
- + mDeviceAdminComponent.flattenToString());
- mPoSet = mDoSet = false;
- }
- }
-
- @Test
- public void testCanSuspendWhenProfileOwner() throws IOException {
- assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
- assertTrue("Profile-owner could not be set", setProfileOwner());
- suspendTestPackage(null, null, null);
- }
-
- @Test
- public void testCanSuspendWhenDeviceOwner() throws IOException {
- assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
- assertTrue("Device-owner could not be set", setDeviceOwner());
- suspendTestPackage(null, null, null);
- }
-
- @Test
- public void testPackageUnsuspendedOnAddingDeviceOwner() throws IOException {
- assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED,
- ACTION_REPORT_MY_PACKAGE_SUSPENDED);
- mAppCommsReceiver.drainPendingBroadcasts();
- suspendTestPackage(null, null, null);
- Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
- assertTrue("Device-owner could not be set", setDeviceOwner());
- intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
- }
-
- @Test
- public void testPackageUnsuspendedOnAddingProfileOwner() throws IOException {
- assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
- mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED,
- ACTION_REPORT_MY_PACKAGE_SUSPENDED);
- mAppCommsReceiver.drainPendingBroadcasts();
- suspendTestPackage(null, null, null);
- Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
- assertTrue("Profile-owner could not be set", setProfileOwner());
- intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
- assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
- }
-
@Test
public void testCameraBlockedOnSuspend() throws Exception {
assertOpBlockedOnSuspend(OP_CAMERA);
@@ -596,13 +294,9 @@ public class SuspendPackagesTest {
@After
public void tearDown() throws IOException {
- mAppCommsReceiver.unregister();
if (mTestCallback != null) {
mLauncherApps.unregisterCallback(mTestCallback);
}
- removeProfileOrDeviceOwner();
- mContext.sendBroadcast(new Intent(ACTION_FINISH_TEST_ACTIVITY)
- .setPackage(TEST_APP_PACKAGE_NAME));
}
private static abstract class StubbedCallback extends LauncherApps.Callback {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 62a0dd4e7c17..419dda57c568 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -62,6 +62,7 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.google.common.truth.Truth.assertThat;
@@ -5482,6 +5483,39 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testRateLimitedToasts_windowsRemoved() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+ setAppInForegroundForToasts(mUid, false);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ Binder token = new Binder();
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+
+ // window token was added when enqueued
+ ArgumentCaptor<Binder> binderCaptor =
+ ArgumentCaptor.forClass(Binder.class);
+ verify(mWindowManagerInternal).addWindowToken(binderCaptor.capture(),
+ eq(TYPE_TOAST), anyInt(), eq(null));
+
+ // but never shown
+ verify(mStatusBar, times(0))
+ .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+
+ // and removed when rate limited
+ verify(mWindowManagerInternal)
+ .removeWindowToken(eq(binderCaptor.getValue()), eq(true), anyInt());
+ }
+
+ @Test
public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws
Exception {
final String testPackage = "testPackageName";
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dc0e02800bb2..2ef59f6ac5c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1422,18 +1422,16 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(config90.orientation, app.getConfiguration().orientation);
assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
- // Make wallaper laid out with the fixed rotation transform.
+ // Associate wallpaper with the fixed rotation transform.
final WindowToken wallpaperToken = mWallpaperWindow.mToken;
wallpaperToken.linkFixedRotationTransform(app);
- mWallpaperWindow.mLayoutNeeded = true;
- performLayout(mDisplayContent);
// Force the negative offset to verify it can be updated.
mWallpaperWindow.mXOffset = mWallpaperWindow.mYOffset = -1;
assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
false /* sync */));
- assertThat(mWallpaperWindow.mXOffset).isGreaterThan(-1);
- assertThat(mWallpaperWindow.mYOffset).isGreaterThan(-1);
+ assertThat(mWallpaperWindow.mXOffset).isNotEqualTo(-1);
+ assertThat(mWallpaperWindow.mYOffset).isNotEqualTo(-1);
// The wallpaper need to animate with transformed position, so its surface position should
// not be reset.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8da8596f47f9..69700052ce74 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -189,11 +189,6 @@ public class DisplayPolicyTests extends WindowTestsBase {
assertEquals(0,
displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null));
- // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, dimming));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, dimming));
-
// Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar));
assertEquals(0, displayPolicy.updateLightNavigationBarLw(
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4220fd78c46f..2fb67d79ccee 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -175,10 +175,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
// Delay for debouncing USB disconnects.
// We often get rapid connect/disconnect events when enabling USB functions,
// which need debouncing.
- private static final int DEVICE_STATE_UPDATE_DELAY = 3000;
-
- // Delay for debouncing USB disconnects on Type-C ports in host mode
- private static final int HOST_STATE_UPDATE_DELAY = 1000;
+ private static final int UPDATE_DELAY = 1000;
// Timeout for entering USB request mode.
// Request is cancelled if host does not configure device within 10 seconds.
@@ -648,7 +645,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
msg.arg1 = connected;
msg.arg2 = configured;
// debounce disconnects to avoid problems bringing up USB tethering
- sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0);
+ sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
}
public void updateHostState(UsbPort port, UsbPortStatus status) {
@@ -663,7 +660,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
removeMessages(MSG_UPDATE_PORT_STATE);
Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args);
// debounce rapid transitions of connect/disconnect on type-c ports
- sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
+ sendMessageDelayed(msg, UPDATE_DELAY);
}
private void setAdbEnabled(boolean enable) {
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index e2d62f868d52..6d9b3210ea70 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -17,7 +17,6 @@
package com.google.android.mms.util;
-import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -25,49 +24,15 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.Build;
import android.util.Log;
-import android.widget.Toast;
public final class SqliteWrapper {
private static final String TAG = "SqliteWrapper";
- private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
- = "unable to open database file";
private SqliteWrapper() {
// Forbidden being instantiated.
}
- // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
- // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
- private static boolean isLowMemory(Context context) {
- if (null == context) {
- return false;
- }
-
- ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
- am.getMemoryInfo(outInfo);
-
- return outInfo.lowMemory;
- }
-
- // FIXME: need to optimize this method.
- private static boolean isLowMemory(SQLiteException e) {
- return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
- }
-
- @UnsupportedAppUsage
- public static void checkSQLiteException(Context context, SQLiteException e) {
- if (isLowMemory(e)) {
- Toast.makeText(context, com.android.internal.R.string.low_memory,
- Toast.LENGTH_SHORT).show();
- } else {
- throw e;
- }
- }
-
@UnsupportedAppUsage
public static Cursor query(Context context, ContentResolver resolver, Uri uri,
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
@@ -75,21 +40,10 @@ public final class SqliteWrapper {
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when query: ", e);
- checkSQLiteException(context, e);
return null;
}
}
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static boolean requery(Context context, Cursor cursor) {
- try {
- return cursor.requery();
- } catch (SQLiteException e) {
- Log.e(TAG, "Catch a SQLiteException when requery: ", e);
- checkSQLiteException(context, e);
- return false;
- }
- }
@UnsupportedAppUsage
public static int update(Context context, ContentResolver resolver, Uri uri,
ContentValues values, String where, String[] selectionArgs) {
@@ -97,7 +51,6 @@ public final class SqliteWrapper {
return resolver.update(uri, values, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when update: ", e);
- checkSQLiteException(context, e);
return -1;
}
}
@@ -109,7 +62,6 @@ public final class SqliteWrapper {
return resolver.delete(uri, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when delete: ", e);
- checkSQLiteException(context, e);
return -1;
}
}
@@ -121,7 +73,6 @@ public final class SqliteWrapper {
return resolver.insert(uri, values);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when insert: ", e);
- checkSQLiteException(context, e);
return null;
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d835ea4a7254..2a600d5afb88 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6145,6 +6145,7 @@ public class TelephonyManager {
DATA_CONNECTED,
DATA_SUSPENDED,
DATA_DISCONNECTING,
+ DATA_HANDOVER_IN_PROGRESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataState{}
@@ -6169,6 +6170,12 @@ public class TelephonyManager {
public static final int DATA_DISCONNECTING = 4;
/**
+ * Data connection state: Handover in progress. The connection is being transited from cellular
+ * network to IWLAN, or from IWLAN to cellular network.
+ */
+ public static final int DATA_HANDOVER_IN_PROGRESS = 5;
+
+ /**
* Used for checking if the SDK version for {@link TelephonyManager#getDataState} is above Q.
*/
@ChangeId
@@ -6184,6 +6191,7 @@ public class TelephonyManager {
* @see #DATA_CONNECTED
* @see #DATA_SUSPENDED
* @see #DATA_DISCONNECTING
+ * @see #DATA_HANDOVER_IN_PROGRESS
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public int getDataState() {
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 9ed230aebfbb..4ff59b568657 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -521,11 +521,11 @@ public class ApnSetting implements Parcelable {
private final boolean mAlwaysOn;
/**
- * Returns the MTU size of the IPv4 mobile interface to which the APN connected. Note this value
- * is used only if MTU size is not provided in {@link DataCallResponse}.
+ * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
+ * up by this APN setting. Note this value will only be used when MTU size is not provided
+ * in {@link DataCallResponse#getMtuV4()} during network bring up.
*
- * @return the MTU size of the APN
- * @hide
+ * @return the MTU size in bytes of the route.
*/
public int getMtuV4() {
return mMtuV4;
@@ -533,10 +533,10 @@ public class ApnSetting implements Parcelable {
/**
* Returns the MTU size of the IPv6 mobile interface to which the APN connected. Note this value
- * is used only if MTU size is not provided in {@link DataCallResponse}.
+ * will only be used when MTU size is not provided in {@link DataCallResponse#getMtuV6()}
+ * during network bring up.
*
- * @return the MTU size of the APN
- * @hide
+ * @return the MTU size in bytes of the route.
*/
public int getMtuV6() {
return mMtuV6;
@@ -1753,25 +1753,25 @@ public class ApnSetting implements Parcelable {
}
/**
- * Set the MTU size of the IPv4 mobile interface to which the APN connected. Note this value
- * is used only if MTU size is not provided in {@link DataCallResponse}.
+ * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
+ * up by this APN setting. Note this value will only be used when MTU size is not provided
+ * in {@link DataCallResponse#getMtuV4()} during network bring up.
*
- * @param mtuV4 the MTU size to set for the APN
- * @hide
+ * @param mtuV4 the MTU size in bytes of the route.
*/
- public Builder setMtuV4(int mtuV4) {
+ public @NonNull Builder setMtuV4(int mtuV4) {
this.mMtuV4 = mtuV4;
return this;
}
/**
- * Set the MTU size of the IPv6 mobile interface to which the APN connected. Note this value
- * is used only if MTU size is not provided in {@link DataCallResponse}.
+ * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv6 routes brought
+ * up by this APN setting. Note this value will only be used when MTU size is not provided
+ * in {@link DataCallResponse#getMtuV6()} during network bring up.
*
- * @param mtuV6 the MTU size to set for the APN
- * @hide
+ * @param mtuV6 the MTU size in bytes of the route.
*/
- public Builder setMtuV6(int mtuV6) {
+ public @NonNull Builder setMtuV6(int mtuV6) {
this.mMtuV6 = mtuV6;
return this;
}
@@ -1779,15 +1779,25 @@ public class ApnSetting implements Parcelable {
/**
* Sets the profile id to which the APN saved in modem.
*
- * @param profileId the profile id to set for the APN
- * @hide
+ * @param profileId the profile id to set for the APN.
*/
- public Builder setProfileId(int profileId) {
+ public @NonNull Builder setProfileId(int profileId) {
this.mProfileId = profileId;
return this;
}
/**
+ * Set if the APN setting should be persistent/non-persistent in modem.
+ *
+ * @param isPersistent {@code true} if this APN setting should be persistent/non-persistent
+ * in modem.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setPersistent(boolean isPersistent) {
+ return setModemCognitive(isPersistent);
+ }
+
+ /**
* Sets if the APN setting is to be set in modem.
*
* @param modemCognitive if the APN setting is to be set in modem
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 479f057eda9c..a166a5d6404c 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -38,8 +38,10 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
- * Description of a mobile data profile used for establishing
- * data connections.
+ * Description of a mobile data profile used for establishing data networks. The data profile
+ * consist an {@link ApnSetting} which is needed for 2G/3G/4G networks bring up, and a
+ * {@link TrafficDescriptor} contains additional information that can be used for 5G standalone
+ * network bring up.
*
* @hide
*/
@@ -113,7 +115,9 @@ public final class DataProfile implements Parcelable {
/**
* @return Id of the data profile.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getProfileId()} instead.
*/
+ @Deprecated
public int getProfileId() {
if (mApnSetting != null) {
return mApnSetting.getProfileId();
@@ -124,9 +128,10 @@ public final class DataProfile implements Parcelable {
/**
* @return The APN (Access Point Name) to establish data connection. This is a string
* specifically defined by the carrier.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getApnName()} instead.
*/
- @NonNull
- public String getApn() {
+ @Deprecated
+ public @NonNull String getApn() {
if (mApnSetting != null) {
return TextUtils.emptyIfNull(mApnSetting.getApnName());
}
@@ -135,7 +140,9 @@ public final class DataProfile implements Parcelable {
/**
* @return The connection protocol defined in 3GPP TS 27.007 section 10.1.1.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getProtocol()} instead.
*/
+ @Deprecated
public @ProtocolType int getProtocolType() {
if (mApnSetting != null) {
return mApnSetting.getProtocol();
@@ -145,7 +152,9 @@ public final class DataProfile implements Parcelable {
/**
* @return The authentication protocol used for this PDP context.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getAuthType()} instead.
*/
+ @Deprecated
public @AuthType int getAuthType() {
if (mApnSetting != null) {
return mApnSetting.getAuthType();
@@ -154,10 +163,11 @@ public final class DataProfile implements Parcelable {
}
/**
- * @return The username for APN. Can be null.
+ * @return The username for APN.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getUser()} instead.
*/
- @Nullable
- public String getUserName() {
+ @Deprecated
+ public @Nullable String getUserName() {
if (mApnSetting != null) {
return mApnSetting.getUser();
}
@@ -165,10 +175,11 @@ public final class DataProfile implements Parcelable {
}
/**
- * @return The password for APN. Can be null.
+ * @return The password for APN.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getPassword()} instead.
*/
- @Nullable
- public String getPassword() {
+ @Deprecated
+ public @Nullable String getPassword() {
if (mApnSetting != null) {
return mApnSetting.getPassword();
}
@@ -232,7 +243,9 @@ public final class DataProfile implements Parcelable {
/**
* @return The supported APN types bitmask.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getApnTypeBitmask()} instead.
*/
+ @Deprecated
public @ApnType int getSupportedApnTypesBitmask() {
if (mApnSetting != null) {
return mApnSetting.getApnTypeBitmask();
@@ -242,7 +255,9 @@ public final class DataProfile implements Parcelable {
/**
* @return The connection protocol on roaming network defined in 3GPP TS 27.007 section 10.1.1.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getRoamingProtocol()} instead.
*/
+ @Deprecated
public @ProtocolType int getRoamingProtocolType() {
if (mApnSetting != null) {
return mApnSetting.getRoamingProtocol();
@@ -252,7 +267,10 @@ public final class DataProfile implements Parcelable {
/**
* @return The bearer bitmask indicating the applicable networks for this data profile.
+ * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getNetworkTypeBitmask()}
+ * instead.
*/
+ @Deprecated
public @NetworkTypeBitMask int getBearerBitmask() {
if (mApnSetting != null) {
return mApnSetting.getNetworkTypeBitmask();
@@ -262,7 +280,8 @@ public final class DataProfile implements Parcelable {
/**
* @return The maximum transmission unit (MTU) size in bytes.
- * @deprecated use {@link #getMtuV4} or {@link #getMtuV6} instead.
+ * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV4()}/
+ * {@link ApnSetting#getMtuV6()} instead.
*/
@Deprecated
public int getMtu() {
@@ -272,7 +291,9 @@ public final class DataProfile implements Parcelable {
/**
* This replaces the deprecated method getMtu.
* @return The maximum transmission unit (MTU) size in bytes, for IPv4.
+ * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV4()} instead.
*/
+ @Deprecated
public int getMtuV4() {
if (mApnSetting != null) {
return mApnSetting.getMtuV4();
@@ -282,7 +303,9 @@ public final class DataProfile implements Parcelable {
/**
* @return The maximum transmission unit (MTU) size in bytes, for IPv6.
+ * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV6()} instead.
*/
+ @Deprecated
public int getMtuV6() {
if (mApnSetting != null) {
return mApnSetting.getMtuV6();
@@ -292,7 +315,9 @@ public final class DataProfile implements Parcelable {
/**
* @return {@code true} if modem must persist this data profile.
+ * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#isPersistent()} instead.
*/
+ @Deprecated
public boolean isPersistent() {
if (mApnSetting != null) {
return mApnSetting.isPersistent();
@@ -320,16 +345,16 @@ public final class DataProfile implements Parcelable {
}
/**
- * @return The APN setting
- * @hide TODO: Remove before T is released.
+ * @return The APN setting {@link ApnSetting}, which is used to establish data network on
+ * 2G/3G/4G.
*/
public @Nullable ApnSetting getApnSetting() {
return mApnSetting;
}
/**
- * @return The traffic descriptor
- * @hide TODO: Remove before T is released.
+ * @return The traffic descriptor {@link TrafficDescriptor}, which can be used to establish
+ * data network on 5G.
*/
public @Nullable TrafficDescriptor getTrafficDescriptor() {
return mTrafficDescriptor;
@@ -539,7 +564,10 @@ public final class DataProfile implements Parcelable {
*
* @param profileId Network domain.
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setProfileId(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setProfileId(int profileId) {
mProfileId = profileId;
return this;
@@ -551,7 +579,10 @@ public final class DataProfile implements Parcelable {
*
* @param apn Access point name
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setApnName(String)} instead.
*/
+ @Deprecated
public @NonNull Builder setApn(@NonNull String apn) {
mApn = apn;
return this;
@@ -562,7 +593,10 @@ public final class DataProfile implements Parcelable {
*
* @param protocolType The connection protocol defined in 3GPP TS 27.007 section 10.1.1.
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setProtocol(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setProtocolType(@ProtocolType int protocolType) {
mProtocolType = protocolType;
return this;
@@ -573,7 +607,10 @@ public final class DataProfile implements Parcelable {
*
* @param authType The authentication type
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setAuthType(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setAuthType(@AuthType int authType) {
mAuthType = authType;
return this;
@@ -584,7 +621,10 @@ public final class DataProfile implements Parcelable {
*
* @param userName The user name
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setUser(String)} instead.
*/
+ @Deprecated
public @NonNull Builder setUserName(@NonNull String userName) {
mUserName = userName;
return this;
@@ -595,7 +635,10 @@ public final class DataProfile implements Parcelable {
*
* @param password The password
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setPassword(String)} (int)} instead.
*/
+ @Deprecated
public @NonNull Builder setPassword(@NonNull String password) {
mPassword = password;
return this;
@@ -628,7 +671,10 @@ public final class DataProfile implements Parcelable {
*
* @param supportedApnTypesBitmask The supported APN types bitmask.
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setApnTypeBitmask(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setSupportedApnTypesBitmask(@ApnType int supportedApnTypesBitmask) {
mSupportedApnTypesBitmask = supportedApnTypesBitmask;
return this;
@@ -639,7 +685,10 @@ public final class DataProfile implements Parcelable {
*
* @param protocolType The connection protocol defined in 3GPP TS 27.007 section 10.1.1.
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setRoamingProtocol(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setRoamingProtocolType(@ProtocolType int protocolType) {
mRoamingProtocolType = protocolType;
return this;
@@ -651,7 +700,10 @@ public final class DataProfile implements Parcelable {
* @param bearerBitmask The bearer bitmask indicating the applicable networks for this data
* profile.
* @return The same instance of the builder.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setNetworkTypeBitmask(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setBearerBitmask(@NetworkTypeBitMask int bearerBitmask) {
mBearerBitmask = bearerBitmask;
return this;
@@ -662,7 +714,9 @@ public final class DataProfile implements Parcelable {
*
* @param mtu The maximum transmission unit (MTU) size in bytes.
* @return The same instance of the builder.
- * @deprecated use {@link #setApnSetting(ApnSetting)} instead.
+ * @deprecated use {@link #setApnSetting(ApnSetting)} and
+ * {@link ApnSetting.Builder#setMtuV4(int)}/{@link ApnSetting.Builder#setMtuV6(int)}
+ * instead.
*/
@Deprecated
public @NonNull Builder setMtu(int mtu) {
@@ -672,11 +726,13 @@ public final class DataProfile implements Parcelable {
/**
* Set the maximum transmission unit (MTU) size in bytes, for IPv4.
- * This replaces the deprecated method setMtu.
*
* @param mtu The maximum transmission unit (MTU) size in bytes.
* @return The same instance of the builder.
+ * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and
+ * {@link ApnSetting.Builder#setMtuV4(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setMtuV4(int mtu) {
mMtuV4 = mtu;
return this;
@@ -687,7 +743,10 @@ public final class DataProfile implements Parcelable {
*
* @param mtu The maximum transmission unit (MTU) size in bytes.
* @return The same instance of the builder.
+ * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and
+ * {@link ApnSetting.Builder#setMtuV6(int)} instead.
*/
+ @Deprecated
public @NonNull Builder setMtuV6(int mtu) {
mMtuV6 = mtu;
return this;
@@ -712,19 +771,23 @@ public final class DataProfile implements Parcelable {
* @param isPersistent {@code true} if this data profile was used to bring up the last
* default (i.e internet) data connection successfully.
* @return The same instance of the builder.
+ * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and
+ * {@link ApnSetting.Builder#setPersistent(boolean)} instead.
*/
+ @Deprecated
public @NonNull Builder setPersistent(boolean isPersistent) {
mPersistent = isPersistent;
return this;
}
/**
- * Set APN setting.
+ * Set the APN setting. Note that if an APN setting is not set here, then either
+ * {@link #setApn(String)} or {@link #setTrafficDescriptor(TrafficDescriptor)} must be
+ * called. Otherwise {@link IllegalArgumentException} will be thrown when {@link #build()}
+ * the data profile.
*
- * @param apnSetting APN setting
- * @return The same instance of the builder
- *
- * @hide // TODO: Remove before T is released.
+ * @param apnSetting The APN setting.
+ * @return The same instance of the builder.
*/
public @NonNull Builder setApnSetting(@NonNull ApnSetting apnSetting) {
mApnSetting = apnSetting;
@@ -732,12 +795,13 @@ public final class DataProfile implements Parcelable {
}
/**
- * Set traffic descriptor.
- *
- * @param trafficDescriptor Traffic descriptor
- * @return The same instance of the builder
+ * Set the traffic descriptor. Note that if a traffic descriptor is not set here, then
+ * either {@link #setApnSetting(ApnSetting)} or {@link #setApn(String)} must be called.
+ * Otherwise {@link IllegalArgumentException} will be thrown when {@link #build()} the data
+ * profile.
*
- * @hide // TODO: Remove before T is released.
+ * @param trafficDescriptor The traffic descriptor.
+ * @return The same instance of the builder.
*/
public @NonNull Builder setTrafficDescriptor(@NonNull TrafficDescriptor trafficDescriptor) {
mTrafficDescriptor = trafficDescriptor;
@@ -745,9 +809,9 @@ public final class DataProfile implements Parcelable {
}
/**
- * Build the DataProfile object
+ * Build the DataProfile object.
*
- * @return The data profile object
+ * @return The data profile object.
*/
public @NonNull DataProfile build() {
if (mApnSetting == null && mApn != null) {
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index e181c62c67e5..68e213b2c00f 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -14,6 +14,7 @@ EMOJI_VS = 0xFE0F
LANG_TO_SCRIPT = {
'as': 'Beng',
+ 'am': 'Latn',
'be': 'Cyrl',
'bg': 'Cyrl',
'bn': 'Beng',
@@ -27,15 +28,18 @@ LANG_TO_SCRIPT = {
'eu': 'Latn',
'fr': 'Latn',
'ga': 'Latn',
+ 'gl': 'Latn',
'gu': 'Gujr',
'hi': 'Deva',
'hr': 'Latn',
'hu': 'Latn',
'hy': 'Armn',
+ 'it': 'Latn',
'ja': 'Jpan',
'kn': 'Knda',
'ko': 'Kore',
'la': 'Latn',
+ 'lt': 'Latn',
'ml': 'Mlym',
'mn': 'Cyrl',
'mr': 'Deva',
@@ -48,6 +52,7 @@ LANG_TO_SCRIPT = {
'ta': 'Taml',
'te': 'Telu',
'tk': 'Latn',
+ 'uk': 'Latn',
}
def lang_to_script(lang_code):